Test framework refactoring

* Adds test/helper.rb and moves mock_app and other code specific
  to testing the framework out of Sinatra::Test.
* Do not require test/unit. The sinatra/test/unit,
  sinatra/test/spec, and sinatra/test/rspec files can be used to
  choose the framework.
* Add Sinatra::TestHarness, which should act similar to the
  Rack::Session proposal here: http://gist.github.com/41270
* Update the README with information on using the different test
  frameworks.
This commit is contained in:
Ryan Tomayko 2009-01-13 09:53:53 -08:00
parent 9482a913a1
commit c00a25ee41
25 changed files with 177 additions and 202 deletions

View File

@ -380,59 +380,61 @@ typically don't have to +use+ them explicitly.
== Testing
=== Test/Unit
The Sinatra::Test module includes a variety of helper methods for testing
your Sinatra app. Sinatra includes support for Test::Unit, test-spec, RSpec,
and Bacon through separate source files.
=== Test::Unit
require 'rubygems'
require 'sinatra'
require 'sinatra/test/unit'
require 'my_sinatra_app'
class MyAppTest < Test::Unit::TestCase
def test_my_default
get_it '/'
get '/'
assert_equal 'My Default Page!', @response.body
end
def test_with_agent
get_it '/', :agent => 'Songbird'
get '/', :agent => 'Songbird'
assert_equal 'You're in Songbird!', @response.body
end
...
end
=== Test/Spec
=== Test::Spec
Install the test-spec gem and require <tt>'sinatra/test/spec'</tt> before
your app:
require 'rubygems'
require 'sinatra'
require 'sinatra/test/spec'
require 'my_sinatra_app'
describe 'My app' do
it "should show a default page" do
get_it '/'
get '/'
should.be.ok
body.should.equal 'My Default Page!'
end
...
end
=== RSpec
require 'rubygems'
require 'spec'
Install the rspec gem and require <tt>'sinatra/test/rspec'</tt> before
your app:
require 'sinatra'
require 'sinatra/test/rspec'
require 'my_sinatra_app'
describe 'My app' do
it 'should show a default page' do
get_it '/'
get '/'
@response.should be_ok
@response.body.should == 'My Default Page!'
end

View File

@ -1,7 +1,6 @@
require File.dirname(__FILE__) + '/helper'
context "Simple Events" do
def simple_request_hash(method, path)
Rack::Request.new({
'REQUEST_METHOD' => method.to_s.upcase,
@ -14,16 +13,16 @@ context "Simple Events" do
def invoke_simple(path, request_path, &b)
params = nil
mock_app {
get path do
params = self.params
b.call if b
end
}
get path do
params = self.params
b.call if b
end
get_it request_path
MockResult.new(b, params)
end
setup { Sinatra.application = nil }
specify "return last value" do
block = Proc.new { 'Simple' }
result = invoke_simple('/', '/', &block)
@ -38,6 +37,7 @@ context "Simple Events" do
result.params.should.equal "foo" => 'a', "bar" => 'b'
# unscapes
Sinatra.application = nil
result = invoke_simple('/:foo/:bar', '/a/blake%20mizerany')
result.should.not.be.nil
result.params.should.equal "foo" => 'a', "bar" => 'blake mizerany'
@ -48,14 +48,17 @@ context "Simple Events" do
result.should.not.be.nil
result.params.should.equal "foo" => 'a', "bar" => 'b'
Sinatra.application = nil
result = invoke_simple('/?:foo?/?:bar?', '/a/')
result.should.not.be.nil
result.params.should.equal "foo" => 'a', "bar" => nil
Sinatra.application = nil
result = invoke_simple('/?:foo?/?:bar?', '/a')
result.should.not.be.nil
result.params.should.equal "foo" => 'a', "bar" => nil
Sinatra.application = nil
result = invoke_simple('/:foo?/?:bar?', '/')
result.should.not.be.nil
result.params.should.equal "foo" => nil, "bar" => nil

View File

@ -13,9 +13,18 @@ require 'sinatra/test'
require 'sinatra/test/unit'
require 'sinatra/test/spec'
module Sinatra::Test
# we need to remove the new test helper methods since they conflict with
# the top-level methods of the same name.
%w(get head post put delete).each do |verb|
remove_method verb
end
include Sinatra::Delegator
end
class Test::Unit::TestCase
include Sinatra::Test
def setup
@app = lambda { |env| Sinatra::Application.call(env) }
end
include Sinatra::Test
end

View File

@ -1,112 +1,110 @@
require 'sinatra/base'
require 'test/unit'
module Sinatra::Test
include Rack::Utils
module Sinatra
attr_reader :app, :request, :response
module Test
include Rack::Utils
def mock_app(base=Sinatra::Base, &block)
@app = Sinatra.new(base, &block)
end
attr_reader :app, :request, :response
undef request if method_defined?(:request)
def request(verb, path, *args)
fail "@app not set - cannot make request" if @app.nil?
@request = Rack::MockRequest.new(@app)
opts, input =
case args.size
when 2 # input, env
input, env = args
if input.kind_of?(Hash) # params, env
[env, param_string(input)]
def test_request(verb, path, *args)
@app = Sinatra::Application if @app.nil? && defined?(Sinatra::Application)
fail "@app not set - cannot make request" if @app.nil?
@request = Rack::MockRequest.new(@app)
opts, input =
case args.size
when 2 # input, env
input, env = args
if input.kind_of?(Hash) # params, env
[env, param_string(input)]
else
[env, input]
end
when 1 # params
if (data = args.first).kind_of?(Hash)
env = (data.delete(:env) || {})
[env, param_string(data)]
else
[{}, data]
end
when 0
[{}, '']
else
[env, input]
raise ArgumentError, "zero, one, or two arguments expected"
end
when 1 # params
if (data = args.first).kind_of?(Hash)
env = (data.delete(:env) || {})
[env, param_string(data)]
else
[{}, data]
end
when 0
[{}, '']
else
raise ArgumentError, "zero, one, or two arguments expected"
opts = rack_opts(opts)
opts[:input] ||= input
yield @request if block_given?
@response = @request.request(verb, path, opts)
end
def get(path, *args, &b) ; test_request('GET', path, *args, &b) ; end
def head(path, *args, &b) ; test_request('HEAD', path, *args, &b) ; end
def post(path, *args, &b) ; test_request('POST', path, *args, &b) ; end
def put(path, *args, &b) ; test_request('PUT', path, *args, &b) ; end
def delete(path, *args, &b) ; test_request('DELETE', path, *args, &b) ; end
def follow!
test_request 'GET', @response.location
end
def body
@response.body
end
def status
@response.status
end
RACK_OPT_NAMES = {
:accept => "HTTP_ACCEPT",
:agent => "HTTP_USER_AGENT",
:host => "HTTP_HOST",
:session => "HTTP_COOKIE",
:cookies => "HTTP_COOKIE",
:content_type => "CONTENT_TYPE"
}
def rack_opts(opts)
opts.inject({}) do |hash,(key,val)|
key = RACK_OPT_NAMES[key] || key
hash[key] = val
hash
end
opts = rack_opts(opts)
opts[:input] ||= input
yield @request if block_given?
@response = @request.request(verb, path, opts)
end
end
def get(path, *args, &b) ; request('GET', path, *args, &b) ; end
def head(path, *args, &b) ; request('HEAD', path, *args, &b) ; end
def post(path, *args, &b) ; request('POST', path, *args, &b) ; end
def put(path, *args, &b) ; request('PUT', path, *args, &b) ; end
def delete(path, *args, &b) ; request('DELETE', path, *args, &b) ; end
def env_for(opts={})
opts = rack_opts(opts)
Rack::MockRequest.env_for(opts)
end
def follow!
request 'GET', @response.location
end
def param_string(hash)
hash.map { |pair| pair.map{|v|escape(v)}.join('=') }.join('&')
end
def should
@response.should
end
if defined? Sinatra::Compat
# Deprecated. Use: "get" instead of "get_it".
%w(get head post put delete).each do |verb|
alias_method "#{verb}_it", verb
end
def body
@response.body
end
def status
@response.status
end
RACK_OPT_NAMES = {
:accept => "HTTP_ACCEPT",
:agent => "HTTP_USER_AGENT",
:host => "HTTP_HOST",
:session => "HTTP_COOKIE",
:cookies => "HTTP_COOKIE",
:content_type => "CONTENT_TYPE"
}
def rack_opts(opts)
opts.inject({}) do |hash,(key,val)|
key = RACK_OPT_NAMES[key] || key
hash[key] = val
hash
# Deprecated. Tests no longer delegate missing methods to the
# mock response. Use: @response
def method_missing(name, *args, &block)
if @response && @response.respond_to?(name)
@response.send(name, *args, &block)
else
super
end
end
end
end
def env_for(opts={})
opts = rack_opts(opts)
Rack::MockRequest.env_for(opts)
end
class TestHarness
include Test
def param_string(hash)
hash.map { |pair| pair.map{|v|escape(v)}.join('=') }.join('&')
end
if defined? Sinatra::Compat
# Deprecated. Use: "get" instead of "get_it".
%w(get head post put delete).each do |verb|
alias_method "#{verb}_it", verb
remove_method verb
end
include Sinatra::Delegator
# Deprecated. Tests no longer delegate missing methods to the
# mock response. Use: @response
def method_missing(name, *args, &block)
if @response && @response.respond_to?(name)
@response.send(name, *args, &block)
else
super
end
def initialize(app=nil)
@app = app || Sinatra::Application
end
end
end

View File

@ -1,2 +1,9 @@
require 'sinatra/test'
require 'spec/interop/test'
Sinatra::Default.set(
:env => :test,
:run => false,
:raise_errors => true,
:logging => false
)

View File

@ -1,2 +1,9 @@
require 'test/spec'
require 'sinatra/test'
require 'sinatra/test/unit'
module Sinatra::Test
def should
@response.should
end
end

View File

@ -1,5 +1,5 @@
require 'test/unit'
require 'sinatra/test'
require 'test/unit'
Test::Unit::TestCase.send :include, Sinatra::Test

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Sinatra::Base' do
include Sinatra::Test
it 'includes Rack::Utils' do
Sinatra::Base.should.include Rack::Utils
end

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe "Builder Templates" do
include Sinatra::Test
def builder_app(&block)
mock_app {
set :views, File.dirname(__FILE__) + '/views'

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe "ERB Templates" do
include Sinatra::Test
def erb_app(&block)
mock_app {
set :views, File.dirname(__FILE__) + '/views'

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe "Filters" do
include Sinatra::Test
it "executes filters in the order defined" do
count = 0
mock_app do

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe "HAML Templates" do
include Sinatra::Test
def haml_app(&block)
mock_app {
set :views, File.dirname(__FILE__) + '/views'

16
test/helper.rb Normal file
View File

@ -0,0 +1,16 @@
require 'test/spec'
$:.unshift File.dirname(File.dirname(__FILE__)) + '/lib'
require 'sinatra/base'
require 'sinatra/test'
require 'sinatra/test/spec'
module Sinatra::Test
# Sets up a Sinatra::Base subclass defined with the block
# given. Used in setup or individual spec methods to establish
# the application.
def mock_app(base=Sinatra::Base, &block)
@app = Sinatra.new(base, &block)
end
end

View File

@ -1,10 +1,4 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
class Test::Unit::TestCase
include Sinatra::Test
end
require File.dirname(__FILE__) + '/helper'
describe 'Sinatra::Helpers' do
describe '#status' do

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Exception Mappings' do
include Sinatra::Test
class FooError < RuntimeError
end

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe "Middleware" do
include Sinatra::Test
before do
@app = mock_app(Sinatra::Default) {
get '/*' do

View File

@ -1,13 +1,7 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Options' do
include Sinatra::Test
before do
@app = Class.new(Sinatra::Base)
end
before { @app = Class.new(Sinatra::Base) }
it 'sets options to literal values' do
@app.set(:foo, 'bar')

View File

@ -1,13 +1,9 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
$reload_count = 0
$reload_app = nil
describe "Reloading" do
include Sinatra::Test
before {
@app = mock_app(Sinatra::Default)
$reload_app = @app

View File

@ -1,6 +1,4 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Sinatra::Request' do
it 'responds to #user_agent' do

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Result Handling' do
include Sinatra::Test
it "sets response.body when result is a String" do
mock_app {
get '/' do

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe "Routing" do
include Sinatra::Test
%w[get put post delete head].each do |verb|
it "defines #{verb.upcase} request handlers with #{verb}" do
mock_app {

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe "Sass Templates" do
include Sinatra::Test
def sass_app(&block)
mock_app {
set :views, File.dirname(__FILE__) + '/views'

View File

@ -1,6 +1,4 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Sinatra' do
it 'creates a new Sinatra::Base subclass on new' do

View File

@ -1,13 +1,10 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Static' do
include Sinatra::Test
F = ::File
before do
@app = mock_app {
mock_app {
set :static, true
set :public, F.dirname(__FILE__)
}

View File

@ -1,10 +1,6 @@
require 'test/spec'
require 'sinatra/base'
require 'sinatra/test'
require File.dirname(__FILE__) + '/helper'
describe 'Templating' do
include Sinatra::Test
def render_app(&block)
mock_app {
def render_test(template, data, options, &block)