diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/testing/integration.rb')
-rw-r--r-- | actionpack/lib/action_dispatch/testing/integration.rb | 181 |
1 files changed, 138 insertions, 43 deletions
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index f7f898288b..f4534b4173 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -2,6 +2,7 @@ require 'stringio' require 'uri' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/object/try' +require 'active_support/core_ext/string/strip' require 'rack/test' require 'minitest' @@ -70,7 +71,7 @@ module ActionDispatch end # Performs an XMLHttpRequest request with the given parameters, mirroring - # a request from the Prototype library. + # an AJAX request made from JavaScript. # # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart @@ -80,7 +81,7 @@ module ActionDispatch # # xhr :get, '/feed', params: { since: 201501011400 } def xml_http_request(request_method, path, *args) - if kwarg_request?(*args) + if kwarg_request?(args) params, headers, env = args.first.values_at(:params, :headers, :env) else params = args[0] @@ -130,35 +131,35 @@ module ActionDispatch # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. def get_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:get, path, *args) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. def post_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:post, path, *args) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. def patch_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:patch, path, *args) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. def put_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:put, path, *args) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. def delete_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:delete, path, *args) end end @@ -290,16 +291,16 @@ module ActionDispatch end def process_with_kwargs(http_method, path, *args) - if kwarg_request?(*args) + if kwarg_request?(args) process(http_method, path, *args) else - non_kwarg_request_warning if args.present? + non_kwarg_request_warning if args.any? process(http_method, path, { params: args[0], headers: args[1] }) end end REQUEST_KWARGS = %i(params headers env xhr) - def kwarg_request?(*args) + def kwarg_request?(args) args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } end @@ -320,19 +321,28 @@ module ActionDispatch end # Performs the actual request. - def process(method, path, params: nil, headers: nil, env: nil, xhr: false) + def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) + request_encoder = RequestEncoder.encoder(as) + if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme - host! "#{location.host}:#{location.port}" if location.host - path = location.query ? "#{location.path}?#{location.query}" : location.path + if url_host = location.host + default = Rack::Request::DEFAULT_PORTS[location.scheme] + url_host += ":#{location.port}" if default != location.port + host! url_host + end + path = request_encoder.append_format_to location.path + path = location.query ? "#{path}?#{location.query}" : path + else + path = request_encoder.append_format_to path end hostname, port = host.split(':') request_env = { :method => method, - :params => params, + :params => request_encoder.encode_params(params), "SERVER_NAME" => hostname, "SERVER_PORT" => port || (https? ? "443" : "80"), @@ -342,22 +352,22 @@ module ActionDispatch "REQUEST_URI" => path, "HTTP_HOST" => host, "REMOTE_ADDR" => remote_addr, - "CONTENT_TYPE" => "application/x-www-form-urlencoded", + "CONTENT_TYPE" => request_encoder.content_type, "HTTP_ACCEPT" => accept } if xhr headers ||= {} headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + headers['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ') end # this modifies the passed request_env directly if headers.present? - Http::Headers.new(request_env).merge!(headers) + Http::Headers.from_hash(request_env).merge!(headers) end if env.present? - Http::Headers.new(request_env).merge!(env) + Http::Headers.from_hash(request_env).merge!(env) end session = Rack::Test::Session.new(_mock_session) @@ -370,10 +380,12 @@ module ActionDispatch @request = ActionDispatch::Request.new(session.last_request.env) response = _mock_session.last_response @response = ActionDispatch::TestResponse.from_response(response) + @response.request = @request + @response.response_parser = RequestEncoder.parser(@response.content_type) @html_document = nil @url_options = nil - @controller = session.last_request.env['action_controller.instance'] + @controller = @request.controller_instance response.status end @@ -381,6 +393,56 @@ module ActionDispatch def build_full_uri(path, env) "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" end + + class RequestEncoder # :nodoc: + @encoders = {} + + attr_reader :response_parser + + def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false) + @mime = Mime[mime_name] + + unless @mime + raise ArgumentError, "Can't register a request encoder for " \ + "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." + end + + @url_encoded_form = url_encoded_form + @path_format = ".#{@mime.symbol}" unless @url_encoded_form + @response_parser = response_parser || -> body { body } + @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc + end + + def append_format_to(path) + path << @path_format unless @url_encoded_form + path + end + + def content_type + @mime.to_s + end + + def encode_params(params) + @param_encoder.call(params) + end + + def self.parser(content_type) + mime = Mime::Type.lookup(content_type) + encoder(mime ? mime.ref : nil).response_parser + end + + def self.encoder(name) + @encoders[name] || WWWFormEncoder + end + + def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) + @encoders[mime_name] = new(mime_name, param_encoder, response_parser) + end + + register_encoder :json, response_parser: -> body { JSON.parse(body) } + + WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true) + end end module Runner @@ -388,8 +450,16 @@ module ActionDispatch APP_SESSIONS = {} - def app - @app ||= nil + attr_reader :app + + def before_setup # :nodoc: + @app = nil + @integration_session = nil + super + end + + def integration_session + @integration_session ||= create_session(app) end # Reset the current session. This is useful for testing multiple sessions @@ -417,12 +487,9 @@ module ActionDispatch %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| - reset! unless integration_session - # reset the html_document variable, except for cookies/assigns calls unless method == 'cookies' || method == 'assigns' @html_document = nil - reset_template_assertion end integration_session.__send__(method, *args).tap do @@ -450,19 +517,16 @@ module ActionDispatch # Copy the instance variables from the current session instance into the # test instance. def copy_session_variables! #:nodoc: - return unless integration_session @controller = @integration_session.controller @response = @integration_session.response @request = @integration_session.request end def default_url_options - reset! unless integration_session integration_session.default_url_options end def default_url_options=(options) - reset! unless integration_session integration_session.default_url_options = options end @@ -472,7 +536,6 @@ module ActionDispatch # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) - reset! unless integration_session if integration_session.respond_to?(sym) integration_session.__send__(sym, *args, &block).tap do copy_session_variables! @@ -481,11 +544,6 @@ module ActionDispatch super end end - - private - def integration_session - @integration_session ||= nil - end end end @@ -508,8 +566,8 @@ module ActionDispatch # assert_equal 200, status # # # post the login and follow through to the home page - # post "/login", username: people(:jamis).username, - # password: people(:jamis).password + # post "/login", params: { username: people(:jamis).username, + # password: people(:jamis).password } # follow_redirect! # assert_equal 200, status # assert_equal "/home", path @@ -548,7 +606,7 @@ module ActionDispatch # end # # def speak(room, message) - # xml_http_request "/say/#{room.id}", message: message + # post "/say/#{room.id}", xhr: true, params: { message: message } # assert(...) # ... # end @@ -558,8 +616,8 @@ module ActionDispatch # open_session do |sess| # sess.extend(CustomAssertions) # who = people(who) - # sess.post "/login", username: who.username, - # password: who.password + # sess.post "/login", params: { username: who.username, + # password: who.password } # assert(...) # end # end @@ -578,14 +636,15 @@ module ActionDispatch # get "/login" # assert_response :success # - # post_via_redirect "/login", username: users(:david).username, password: users(:david).password + # post "/login", params: { username: users(:david).username, password: users(:david).password } + # follow_redirect! # assert_equal '/welcome', path # assert_equal 'Welcome david!', flash[:notice] # # https!(false) # get "/articles/all" # assert_response :success - # assert assigns(:articles) + # assert_select 'h1', 'Articles' # end # end # @@ -624,7 +683,7 @@ module ActionDispatch # def browses_site # get "/products/all" # assert_response :success - # assert assigns(:products) + # assert_select 'h1', 'Products' # end # end # @@ -633,13 +692,46 @@ module ActionDispatch # sess.extend(CustomDsl) # u = users(user) # sess.https! - # sess.post "/login", username: u.username, password: u.password + # sess.post "/login", params: { username: u.username, password: u.password } # assert_equal '/welcome', sess.path # sess.https!(false) # end # end # end # + # You can also test your JSON API easily by setting what the request should + # be encoded as: + # + # require 'test_helper' + # + # class ApiTest < ActionDispatch::IntegrationTest + # test 'creates articles' do + # assert_difference -> { Article.count } do + # post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json + # end + # + # assert_response :success + # assert_equal({ id: Arcticle.last.id, title: 'Ahoy!' }, response.parsed_body) + # end + # end + # + # The `as` option sets the format to JSON, sets the content type to + # 'application/json' and encodes the parameters as JSON. + # + # Calling `parsed_body` on the response parses the response body as what + # the last request was encoded as. If the request wasn't encoded `as` something, + # it's the same as calling `body`. + # + # For any custom MIME Types you've registered, you can even add your own encoders with: + # + # ActionDispatch::IntegrationTest.register_encoder :wibble, + # param_encoder: -> params { params.to_wibble }, + # response_parser: -> body { body } + # + # Where `param_encoder` defines how the params should be encoded and + # `response_parser` defines how the response body should be parsed through + # `parsed_body`. + # # Consult the Rails Testing Guide for more. class IntegrationTest < ActiveSupport::TestCase @@ -662,12 +754,15 @@ module ActionDispatch end def url_options - reset! unless integration_session integration_session.url_options end def document_root_element html_document.root end + + def self.register_encoder(*args) + Integration::Session::RequestEncoder.register_encoder(*args) + end end end |