class AssertResponse # error class, raised when a 404 status but another status was expected class Status404 < Exception ; end # Hash to collection available content-types (prefilled with the Rack::Mime::MIME_TYPES) # # @see AssertResponse#method_missing CONTENT_TYPES = {} # Adds custom content_types to # {AssertResponse::CONTENT_TYPES}. We would then have all the test methods for the content-type # @param [Symbol] name name of the content_type # @param [String] content_type content_type (mime type, eg. "text/html") def AssertResponse.add_content_type(name, content_type) CONTENT_TYPES.update(name => content_type) end # add all Rack::Mime::MIME_TYPES as content_types Rack::Mime::MIME_TYPES.each do |fileext,mime| AssertResponse.add_content_type fileext.sub(/^\./, '').to_sym, mime end # add content_type 'text/plain' as :text AssertResponse.add_content_type :text, 'text/plain' # Creates a new {AssertResponse} Object. Usually you will want to +instance_exec+ some code on it. # # @param [Object] delegate_to the test object that has all the +assert_equal+, +assert_match+ and +assert+ methods that we need # @param [MockResponse, Response] response the response object that is checked def initialize(delegate_to, response, &code) @delegate_to = delegate_to @response = response @error = nil check_for_error() end # for each content-type in {AssertResponse::CONTENT_TYPES} these methods are served by method_missing # * +is_[content_type]+ that checks if the response has the content-type # * +[content_type]+ that checks the content-type and matches the body against the given pattern and checks for status 200 # * +not_found_[content_type]+ that checks the content-type, matches the body against the pattern and checks for status 404 # further unknown methods are delegated to +@delegate_to+ def method_missing(meth, *args, &code) case meth.to_s when /^(is|found)_(.+)$/ # is_[content_type] methods if ctype = CONTENT_TYPES[$2.to_sym] ok() return content_type(ctype) end when /^not_found_(.+)$/ # not_found_[content_type] methods if ctype = CONTENT_TYPES[$1.to_sym] content_type ctype return body(args.first, 404) end else if ctype = CONTENT_TYPES[meth] # [content_type] methods ok() content_type ctype return body(args.first) end end @delegate_to.send meth, *args, &code end # Checks if status is 200. def ok() status 200 end # Checks if the status is +status+. For status other then 5xx and error is raised if the response has an error. # For status other than 404 an error Status404 is thrown if response status is 404 # # @params [Integer] status the expected status def status(status) raise_error() unless status =~ /^5/ or @response.errors.empty? Kernel.raise(Status404, "not found") unless status == 404 or @response.status != 404 assert_equal(status, @response.status) end # Checks if the response status is 404. def not_found() status 404 end # Checks if the status is 302, and the header "Location" matches the +pattern+ # # @param [String,Regexp] pattern the pattern to match against. def redirect(pattern) status 302 header 'Location', pattern end # Checks if the header +key+ exists and / or if it matches +pattern+. # # @param [String] key the key to look for # @param [String,Regexp,:exists] pattern the pattern to match against. +:exists+ just checks for existance of +key+. def header(key, pattern=:exists) if pattern == :exists assert !@response.headers[key].to_s.empty?, "Header '#{key}' not found" else assert_match normalize_pattern(pattern), @response.headers[key].to_s end end # Check if the +Content-Type+ matches +pattern+. Checks also if request is ok (see {#ok}). # # @param [String,Regexp] pattern the pattern to match against. def content_type(pattern) header 'Content-Type', pattern end # Check if body matches +pattern+ and status is +status+. # # @param [String,Regexp] pattern the pattern to match against. # @params [Integer] status the expected status (default 200, see {#ok}) def body(pattern, status=200) status(status) assert_match normalize_pattern(pattern), @response.body end # Check if an error was raised. # # @param [Class] exception_class also check if error is_a? +exception_class+ # @param [String,Regexp] pattern also check if the error message matches +pattern+ def raises(exception_class=nil, pattern=nil) assert_equal exception_class, @error.class if exception_class assert_match normalize_pattern(pattern), @error.message if pattern assert !@error.nil? end private def normalize_pattern(pattern) pattern.is_a?(Regexp) ? pattern : /#{Regexp.escape(pattern.to_s)}/ end def check_for_error normalize_error unless @response.errors.to_s.empty? end def normalize_error message, backtrace = @response.errors.split "\n", 2 backtrace = backtrace.split err_klass, message = message.split '-', 2 @error = Module.const_get(err_klass.strip.to_sym).new(message.strip) @error.set_backtrace backtrace @error end def raise_error() Kernel.raise @error end # these methods are included in Rack::Test::Methods to be used in a Test Class # # call assert_response with a code block to use the DSL (methods from {AssertResponse}) # or use a method like assert_response_xxx where xxx is the name of the method from {AssertResponse} you want to call # @see AssertResponse # # @example with Test::Unit, simple # require 'rack/test' # require 'assert-response' # # class TestMe < Test::Unit::Testcase # include Rack::Test::Methods # def app # MyApp # end # # def test_something # get '/works' # assert_response_html "should really work" # end # end # # @example without including +Rack::Test::Methods+ # require 'rack/test' # require 'assert-response' # # class AppWrapper # include Rack::Test::Methods # def app # MyApp # end # end # # class TestMe < Test::Unit::Testcase # include AssertResponse::Methods # # def test_something # server = AppWrapper.new # server.get '/works' # assert_response server.last_response do # html "should really work" # end # end # end # # @example with *Minitest*, simple # require 'rack/test' # require 'assert-response' # include Rack::Test::Methods # # # write your tests here # it "should work" do # get '/works' # assert_response_html "should really work" # end # # @example without including +Rack::Test::Methods+ # require 'rack/test' # require 'assert-response' # # class AppWrapper # include Rack::Test::Methods # def app # MyApp # end # end # # include AssertResponse::Methods # # describe "my app" do # before do # @app = AppWrapper.new # end # # it "should work" do # @app.get '/works' # assert_response(@app.last_response) do # html "should really work" # end # end # end module Methods # route assert_response_ methods to {AssertResponse} # @see AssertResponse def method_missing(meth, *args, &code) if meth.to_s =~ /^assert_response_(.+)$/ AssertResponse.new(self, last_response).send($1.to_sym, *args) else super end end # creates an {AssertResponse} Object and +instance_exec+ the code (DSL) in it # @see AssertResponse def assert_response(response=last_response, &code) file, line, rest = caller[0].split(':', 3) AssertResponse.new(self, response).instance_exec(file, line.to_i, &code) end end end module Rack::Test::Methods include AssertResponse::Methods end