aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/testing
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/testing')
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb49
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb28
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb103
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb6
6 files changed, 168 insertions, 20 deletions
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
new file mode 100644
index 0000000000..3fb81ff083
--- /dev/null
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -0,0 +1,49 @@
+module ActionDispatch
+ # This is a class that abstracts away an asserted response.
+ # It purposely does not inherit from Response, because it doesn't need it.
+ # That means it does not have headers or a body.
+ #
+ # As an input to the initializer, we take a Fixnum, a String, or a Symbol.
+ # If it's a Fixnum or String, we figure out what its symbolized name.
+ # If it's a Symbol, we figure out what its corresponding code is.
+ # The resulting code will be a Fixnum, for real HTTP codes, and it will
+ # be a String for the pseudo-HTTP codes, such as:
+ # :success, :missing, :redirect and :error
+ class AssertionResponse
+ attr_reader :code, :name
+
+ GENERIC_RESPONSE_CODES = { # :nodoc:
+ success: "2XX",
+ missing: "404",
+ redirect: "3XX",
+ error: "5XX"
+ }
+
+ def initialize(code_or_name)
+ if code_or_name.is_a?(Symbol)
+ @name = code_or_name
+ @code = code_from_name(code_or_name)
+ else
+ @name = name_from_code(code_or_name)
+ @code = code_or_name
+ end
+
+ raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
+ raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
+ end
+
+ def code_and_name
+ "#{code}: #{name}"
+ end
+
+ private
+
+ def code_from_name(name)
+ GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
+ end
+
+ def name_from_code(code)
+ GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index c138660a21..cd55b7d975 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,4 +1,3 @@
-
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -29,18 +28,10 @@ module ActionDispatch
def assert_response(type, message = nil)
message ||= generate_response_message(type)
- if Symbol === type
- if [:success, :missing, :redirect, :error].include?(type)
- assert @response.send(RESPONSE_PREDICATES[type]), message
- else
- code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
- if code.nil?
- raise ArgumentError, "Invalid response type :#{type}"
- end
- assert_equal code, @response.response_code, message
- end
+ if RESPONSE_PREDICATES.keys.include?(type)
+ assert @response.send(RESPONSE_PREDICATES[type]), message
else
- assert_equal type, @response.response_code, message
+ assert_equal AssertionResponse.new(type).code, @response.response_code, message
end
end
@@ -85,8 +76,9 @@ module ActionDispatch
end
end
- def generate_response_message(type, code = @response.response_code)
- "Expected response to be a <#{type}>, but was a <#{code}>"
+ def generate_response_message(expected, actual = @response.response_code)
+ "Expected response to be a <#{code_with_name(expected)}>,"\
+ " but was a <#{code_with_name(actual)}>"
.concat location_if_redirected
end
@@ -95,6 +87,14 @@ module ActionDispatch
location = normalize_argument_to_redirection(@response.location)
" redirect to <#{location}>"
end
+
+ def code_with_name(code_or_name)
+ if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
+ code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
+ end
+
+ AssertionResponse.new(code_or_name).code_and_name
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 78ef860548..e7af27463c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -86,6 +86,7 @@ module ActionDispatch
end
# Load routes.rb if it hasn't been loaded.
+ options = options.clone
generated_path, query_string_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject { |k, _| ! query_string_keys.include? k }
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 711ca10419..f4534b4173 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -71,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
@@ -321,7 +321,9 @@ 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
@@ -330,14 +332,17 @@ module ActionDispatch
url_host += ":#{location.port}" if default != location.port
host! url_host
end
- path = location.query ? "#{location.path}?#{location.query}" : location.path
+ 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"),
@@ -347,7 +352,7 @@ 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
}
@@ -376,6 +381,7 @@ module ActionDispatch
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
@@ -387,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
@@ -643,6 +699,39 @@ module ActionDispatch
# 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
@@ -671,5 +760,9 @@ module ActionDispatch
def document_root_element
html_document.root
end
+
+ def self.register_encoder(*args)
+ Integration::Session::RequestEncoder.register_encoder(*args)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index eca0439909..1ecd7d14a7 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -1,6 +1,5 @@
require 'action_dispatch/middleware/cookies'
require 'action_dispatch/middleware/flash'
-require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 4b79a90242..9d4b73a43d 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -18,5 +18,11 @@ module ActionDispatch
# Was there a server-side error?
alias_method :error?, :server_error?
+
+ attr_writer :response_parser # :nodoc:
+
+ def parsed_body
+ @parsed_body ||= @response_parser.call(body)
+ end
end
end