class AssertResponse class Status404 < Exception ; end # for each content-type there is a method # * +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 # see method_missing CONTENT_TYPES = {} CONTENT_TYPES[:html] = 'text/html' CONTENT_TYPES[:css] = 'text/css' CONTENT_TYPES[:js] = 'application/javascript' CONTENT_TYPES[:json] = 'application/json' CONTENT_TYPES[:text] = 'text/plain' # 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 # 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_xxx methods that we want to call # @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 alias :_old_method_missing :method_missing # handles is_[content_type], not_found_[content_type] and [content_type] methods # Delegates unknown methods 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 # # *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 # # 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 # # *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 # # 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 alias_method :__assert_response_method_missing, :method_missing # route assert_response_ methods to 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 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