aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/test_case.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller/test_case.rb')
-rw-r--r--actionpack/lib/action_controller/test_case.rb314
1 files changed, 132 insertions, 182 deletions
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index ecd21f29ce..57921f32b7 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,44 +1,49 @@
-require 'rack/session/abstract/id'
-require 'active_support/core_ext/hash/conversions'
-require 'active_support/core_ext/object/to_query'
-require 'active_support/core_ext/module/anonymous'
-require 'active_support/core_ext/hash/keys'
-require 'action_controller/template_assertions'
-require 'rails-dom-testing'
+# frozen_string_literal: true
+
+require "rack/session/abstract/id"
+require "active_support/core_ext/hash/conversions"
+require "active_support/core_ext/object/to_query"
+require "active_support/core_ext/module/anonymous"
+require "active_support/core_ext/module/redefine_method"
+require "active_support/core_ext/hash/keys"
+require "active_support/testing/constant_lookup"
+require "action_controller/template_assertions"
+require "rails-dom-testing"
module ActionController
- # :stopdoc:
class Metal
include Testing::Functional
end
module Live
- # Disable controller / rendering threads in tests. User tests can access
+ # Disable controller / rendering threads in tests. User tests can access
# the database on the main thread, so they could open a txn, then the
# controller thread will open a new connection and try to access data
- # that's only visible to the main thread's txn. This is the problem in #23483
- remove_method :new_controller_thread
+ # that's only visible to the main thread's txn. This is the problem in #23483.
+ silence_redefinition_of_method :new_controller_thread
def new_controller_thread # :nodoc:
yield
end
end
- # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
+ # ActionController::TestCase will be deprecated and moved to a gem in the future.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
- DEFAULT_ENV.delete 'PATH_INFO'
+ DEFAULT_ENV.delete "PATH_INFO"
def self.new_session
TestSession.new
end
- # Create a new test request with default `env` values
- def self.create
+ attr_reader :controller_class
+
+ # Create a new test request with default `env` values.
+ def self.create(controller_class)
env = {}
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
env["rack.request.cookie_hash"] = {}.with_indifferent_access
- new(default_env.merge(env), new_session)
+ new(default_env.merge(env), new_session, controller_class)
end
def self.default_env
@@ -46,13 +51,14 @@ module ActionController
end
private_class_method :default_env
- def initialize(env, session)
+ def initialize(env, session, controller_class)
super(env)
self.session = session
- self.session_options = TestSession::DEFAULT_OPTIONS
+ self.session_options = TestSession::DEFAULT_OPTIONS.dup
+ @controller_class = controller_class
@custom_param_parsers = {
- xml: lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] }
}
end
@@ -61,7 +67,7 @@ module ActionController
end
def content_type=(type)
- set_header 'CONTENT_TYPE', type
+ set_header "CONTENT_TYPE", type
end
def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
@@ -83,7 +89,7 @@ module ActionController
end
if get?
- if self.query_string.blank?
+ if query_string.blank?
self.query_string = non_path_parameters.to_query
end
else
@@ -91,8 +97,8 @@ module ActionController
self.content_type = ENCODER.content_type
data = ENCODER.build_multipart non_path_parameters
else
- fetch_header('CONTENT_TYPE') do |k|
- set_header k, 'application/x-www-form-urlencoded'
+ fetch_header("CONTENT_TYPE") do |k|
+ set_header k, "application/x-www-form-urlencoded"
end
case content_mime_type.to_sym
@@ -110,8 +116,9 @@ module ActionController
end
end
- set_header 'CONTENT_LENGTH', data.length.to_s
- set_header 'rack.input', StringIO.new(data)
+ data_stream = StringIO.new(data)
+ set_header "CONTENT_LENGTH", data_stream.length.to_s
+ set_header "rack.input", data_stream
end
fetch_header("PATH_INFO") do |k|
@@ -127,7 +134,7 @@ module ActionController
include Rack::Test::Utils
def should_multipart?(params)
- # FIXME: lifted from Rack-Test. We should push this separation upstream
+ # FIXME: lifted from Rack-Test. We should push this separation upstream.
multipart = false
query = lambda { |value|
case value
@@ -152,9 +159,9 @@ module ActionController
private
- def params_parsers
- super.merge @custom_param_parsers
- end
+ def params_parsers
+ super.merge @custom_param_parsers
+ end
end
class LiveTestResponse < Live::Response
@@ -208,10 +215,18 @@ module ActionController
end
# Superclass for ActionController functional tests. Functional tests allow you to
- # test a single controller action per test method. This should not be confused with
- # integration tests (see ActionDispatch::IntegrationTest), which are more like
- # "stories" that can involve multiple controllers and multiple actions (i.e. multiple
- # different HTTP requests).
+ # test a single controller action per test method.
+ #
+ # == Use integration style controller tests over functional style controller tests.
+ #
+ # Rails discourages the use of functional tests in favor of integration tests
+ # (use ActionDispatch::IntegrationTest).
+ #
+ # New Rails applications no longer generate functional style controller tests and they should
+ # only be used for backward compatibility. Integration style controller tests perform actual
+ # requests, whereas functional style controller tests merely simulate a request. Besides,
+ # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
+ # +parsed_body+ for effective testing of controller actions including even API endpoints.
#
# == Basic example
#
@@ -241,7 +256,7 @@ module ActionController
#
# def test_create
# json = {book: { title: "Love Hina" }}.to_json
- # post :create, json
+ # post :create, body: json
# end
#
# == Special instance variables
@@ -261,9 +276,6 @@ module ActionController
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
#
- # (Earlier versions of \Rails required each functional test to subclass
- # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
- #
# == Controller is automatically inferred
#
# ActionController::TestCase will automatically infer the controller under test
@@ -288,7 +300,7 @@ module ActionController
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
- # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
+ # On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
@@ -320,7 +332,6 @@ module ActionController
attr_reader :response, :request
module ClassMethods
-
# Sets the controller class name. Useful if the name can't be inferred from test class.
# Normalizes +controller_class+ before using.
#
@@ -343,7 +354,7 @@ module ActionController
end
def controller_class
- if current_controller_class = self._controller_class
+ if current_controller_class = _controller_class
current_controller_class
else
self.controller_class = determine_default_controller_class(name)
@@ -377,57 +388,42 @@ module ActionController
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
- def get(action, *args)
- res = process_with_kwargs("GET", action, *args)
+ def get(action, **args)
+ res = process(action, method: "GET", **args)
cookies.update res.cookies
res
end
# Simulate a POST request with the given parameters and set/volley the response.
# See +get+ for more details.
- def post(action, *args)
- process_with_kwargs("POST", action, *args)
+ def post(action, **args)
+ process(action, method: "POST", **args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
# See +get+ for more details.
- def patch(action, *args)
- process_with_kwargs("PATCH", action, *args)
+ def patch(action, **args)
+ process(action, method: "PATCH", **args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
# See +get+ for more details.
- def put(action, *args)
- process_with_kwargs("PUT", action, *args)
+ def put(action, **args)
+ process(action, method: "PUT", **args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
# See +get+ for more details.
- def delete(action, *args)
- process_with_kwargs("DELETE", action, *args)
+ def delete(action, **args)
+ process(action, method: "DELETE", **args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
# See +get+ for more details.
- def head(action, *args)
- process_with_kwargs("HEAD", action, *args)
+ def head(action, **args)
+ process(action, method: "HEAD", **args)
end
- def xml_http_request(*args)
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- xhr and xml_http_request methods are deprecated in favor of
- `get :index, xhr: true` and `post :create, xhr: true`
- MSG
-
- @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
- __send__(*args).tap do
- @request.env.delete 'HTTP_X_REQUESTED_WITH'
- @request.env.delete 'HTTP_ACCEPT'
- end
- end
- alias xhr :xml_http_request
-
# Simulate an HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
@@ -440,6 +436,8 @@ module ActionController
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
# - +format+: Request format. Defaults to +nil+. Can be string or symbol.
+ # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
+ # to a mime type.
#
# Example calling +create+ action and sending two params:
#
@@ -456,57 +454,40 @@ module ActionController
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
- def process(action, *args)
+ def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
check_required_ivars
- if kwarg_request?(args)
- parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
- else
- http_method, parameters, session, flash = args
- format = nil
+ http_method = method.to_s.upcase
- if parameters.is_a?(String) && http_method != 'HEAD'
- body = parameters
- parameters = nil
- end
+ @html_document = nil
- if parameters || session || flash
- non_kwarg_request_warning
- end
- end
+ cookies.update(@request.cookies)
+ cookies.update_cookies_from_jar
+ @request.set_header "HTTP_COOKIE", cookies.to_header
+ @request.delete_header "action_dispatch.cookies"
+
+ @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class
+ @response = build_response @response_klass
+ @response.request = @request
+ @controller.recycle!
if body
- @request.set_header 'RAW_POST_DATA', body
+ @request.set_header "RAW_POST_DATA", body
end
- if http_method
- http_method = http_method.to_s.upcase
- else
- http_method = "GET"
+ @request.set_header "REQUEST_METHOD", http_method
+
+ if as
+ @request.content_type = Mime[as].to_s
+ format ||= as
end
- parameters ||= {}
+ parameters = (params || {}).symbolize_keys
if format
parameters[:format] = format
end
- @html_document = nil
-
- self.cookies.update @request.cookies
- self.cookies.update_cookies_from_jar
- @request.set_header 'HTTP_COOKIE', cookies.to_header
- @request.delete_header 'action_dispatch.cookies'
-
- @request = TestRequest.new scrub_env!(@request.env), @request.session
- @response = build_response @response_klass
- @response.request = @request
- @controller.recycle!
-
- @request.set_header 'REQUEST_METHOD', http_method
-
- parameters = parameters.symbolize_keys
-
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
generated_path = generated_path(generated_extras)
query_string_keys = query_parameter_names(generated_extras)
@@ -517,9 +498,9 @@ module ActionController
@request.flash.update(flash || {})
if xhr
- @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
- @request.fetch_header('HTTP_ACCEPT') do |k|
- @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
+ @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
+ @request.fetch_header("HTTP_ACCEPT") do |k|
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
end
end
@@ -527,32 +508,35 @@ module ActionController
@request.set_header k, @controller.config.relative_url_root
end
- @controller.recycle!
- @controller.dispatch(action, @request, @response)
- @request = @controller.request
- @response = @controller.response
-
- @request.delete_header 'HTTP_COOKIE'
+ begin
+ @controller.recycle!
+ @controller.dispatch(action, @request, @response)
+ ensure
+ @request = @controller.request
+ @response = @controller.response
+
+ if @request.have_cookie_jar?
+ unless @request.cookie_jar.committed?
+ @request.cookie_jar.write(@response)
+ cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
+ end
+ end
+ @response.prepare!
- if @request.have_cookie_jar?
- unless @request.cookie_jar.committed?
- @request.cookie_jar.write(@response)
- self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
+ if flash_value = @request.flash.to_session_value
+ @request.session["flash"] = flash_value
+ else
+ @request.session.delete("flash")
end
- end
- @response.prepare!
- if flash_value = @request.flash.to_session_value
- @request.session['flash'] = flash_value
- else
- @request.session.delete('flash')
- end
+ if xhr
+ @request.delete_header "HTTP_X_REQUESTED_WITH"
+ @request.delete_header "HTTP_ACCEPT"
+ end
+ @request.query_string = ""
- if xhr
- @request.delete_header 'HTTP_X_REQUESTED_WITH'
- @request.delete_header 'HTTP_ACCEPT'
+ @response.sent!
end
- @request.query_string = ''
@response
end
@@ -587,7 +571,7 @@ module ActionController
end
end
- @request = TestRequest.create
+ @request = TestRequest.create(@controller.class)
@response = build_response @response_klass
@response.request = @request
@@ -606,71 +590,37 @@ module ActionController
include ActionDispatch::Assertions
class_attribute :_controller_class
setup :setup_controller_request_and_response
+ ActiveSupport.run_load_hooks(:action_controller_test_case, self)
end
private
- def scrub_env!(env)
- env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
- env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
- env.delete 'action_dispatch.request.query_parameters'
- env.delete 'action_dispatch.request.request_parameters'
- env
- end
-
- def process_with_kwargs(http_method, action, *args)
- if kwarg_request?(args)
- args.first.merge!(method: http_method)
- process(action, *args)
- else
- non_kwarg_request_warning if args.any?
-
- args = args.unshift(http_method)
- process(action, *args)
+ def scrub_env!(env)
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
+ env.delete "action_dispatch.request.query_parameters"
+ env.delete "action_dispatch.request.request_parameters"
+ env["rack.input"] = StringIO.new
+ env.delete "CONTENT_LENGTH"
+ env.delete "RAW_POST_DATA"
+ env
end
- end
-
- REQUEST_KWARGS = %i(params session flash method body xhr)
- def kwarg_request?(args)
- args[0].respond_to?(:keys) && (
- (args[0].key?(:format) && args[0].keys.size == 1) ||
- args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
- )
- end
-
- def non_kwarg_request_warning
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- ActionController::TestCase HTTP request methods will accept only
- keyword arguments in future Rails versions.
-
- Examples:
-
- get :show, params: { id: 1 }, session: { user_id: 1 }
- process :update, method: :post, params: { id: 1 }
- MSG
- end
- def document_root_element
- html_document.root
- end
+ def document_root_element
+ html_document.root
+ end
- def check_required_ivars
- # Sanity check for required instance variables so we can give an
- # understandable error message.
- [:@routes, :@controller, :@request, :@response].each do |iv_name|
- if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
- raise "#{iv_name} is nil: make sure you set it in your test's setup method."
+ def check_required_ivars
+ # Sanity check for required instance variables so we can give an
+ # understandable error message.
+ [:@routes, :@controller, :@request, :@response].each do |iv_name|
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
+ end
end
end
- end
-
- def html_format?(parameters)
- return true unless parameters.key?(:format)
- Mime.fetch(parameters[:format]) { Mime['html'] }.html?
- end
end
include Behavior
end
- # :startdoc:
end