Use kwargs in ActionController::TestCase and ActionDispatch::Integration HTTP methods #18323

Merged
merged 1 commit into from Jan 29, 2015

Projects

None yet
@kirs
Contributor
kirs commented Jan 4, 2015

As @dhh suggested, controller HTTP methods are a prime candidate for kwargs.
post url, nil, nil, { a: 'b' } doesn't make sense.
More explicit way would be: post url, params: { y: x }, session: { a: 'b' }.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Jan 4, 2015
actionpack/lib/action_controller/test_case.rb
@@ -593,7 +607,14 @@ def process(action, http_method = 'GET', *args)
@request.env['RAW_POST_DATA'] = args.shift
end
- parameters, session, flash = args
+ if args[0] && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
+ parameters = args[0][:params]
+ session = args[0][:session]
+ flash = args[0][:flash]
+ else
+ parameters, session, flash = args
@rafaelfranca
rafaelfranca Jan 4, 2015 Member

We should deprecate this path or we will not be able to use kwargs for real here in the future.

@rafaelfranca
rafaelfranca Jan 4, 2015 Member

Since we are deprecating all documentation and tests should use the kwarg implementation and we can remove the old implementation from the documentation keeping only a few tests to make sure it works and it is deprecated.

@kirs
kirs Jan 7, 2015 Contributor

Good point, thanks.

Here is rdoc syntax for usual arguments:

- +action+: The controller action to call.
- +http_method+: Request method used to send the http request. Possible values
  are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
- +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
  string that is appropriately encoded (+application/x-www-form-urlencoded+
  or +multipart/form-data+).
- +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+.

Any suggestions how it will look like with kwargs? Couldn't find it in docs.

@dhh
dhh Jan 7, 2015 Member

New form should be:

process action, method: 'GET', params: {}, session: {}, flash: {}

And then we add on with:

post/get/put/patch/delete/head action, params: {}, session: {}, flash: {}

@kirs
kirs Jan 7, 2015 Contributor

Got it. I meant the rdoc syntax for describing kwargs

@dhh
Member
dhh commented Jan 4, 2015

We should do the same treatment to ActionDispatch::IntegrationTest.

@kirs
Contributor
kirs commented Jan 4, 2015

@dhh in the same PR or in the separate?

@rafaelfranca
Member

In the same

On Sun, Jan 4, 2015, 15:04 Kir Shatrov notifications@github.com wrote:

@dhh https://github.com/dhh in the same PR or in the separate?


Reply to this email directly or view it on GitHub
#18323 (comment).

@dhh
Member
dhh commented Jan 4, 2015

We can do a separate PR for that. Just that we don’t want the two APIs to diverge in a release.

On Jan 4, 2015, at 9:04 AM, Kir Shatrov notifications@github.com wrote:

@dhh https://github.com/dhh in the same PR or in the separate?


Reply to this email directly or view it on GitHub #18323 (comment).

@dhh
Member
dhh commented Jan 4, 2015

Ha, I don't really care if it's the same or separate. But yeah, @rafaelfranca is probably right that we might as well treat it as one change. Since it's the same API. Although IntegrationTests also have env.

@rafaelfranca
Member

Yeah, they can be different commits but I'd like to have the whole set of
changes grouped so we can link is release notes or even revert if needed.

On Sun, Jan 4, 2015, 15:08 David Heinemeier Hansson <
notifications@github.com> wrote:

Ha, I don't really care if it's the same or separate. But yeah,
@rafaelfranca https://github.com/rafaelfranca is probably right that we
might as well treat it as one change. Since it's the same API. Although
IntegrationTests also have env.


Reply to this email directly or view it on GitHub
#18323 (comment).

@kirs
Contributor
kirs commented Jan 19, 2015
@kirs kirs changed the title from Use kwargs in ActionController::TestCase HTTP methods to Use kwargs in ActionController::TestCase and ActionDispatch::Integration HTTP methods Jan 19, 2015
@dhh dhh commented on an outdated diff Jan 19, 2015
actionpack/test/controller/integration_test.rb
- render plain: 'ok'
- end
- end
-
- def test_request
- with_routing do |routes|
- routes.draw { get ':action' => FooController }
- get '/ok'
-
- assert_response 200
- assert_equal 'ok', response.body
- assert_equal 'ok', cookies['key']
- end
- end
-end
+# class MetalIntegrationTest < ActionDispatch::IntegrationTest
@dhh
dhh Jan 19, 2015 Member

Why is this test commented out?

@dhh dhh and 1 other commented on an outdated diff Jan 19, 2015
actionpack/test/controller/live_stream_test.rb
end
capture_log_output do |output|
- get :exception_in_view_after_commit, format: :json
@dhh
dhh Jan 19, 2015 Member

format should be a stand-alone thing, but part of the params.

@kirs
kirs Jan 20, 2015 Contributor

Fixed.

@dhh dhh commented on an outdated diff Jan 19, 2015
actionpack/lib/action_dispatch/testing/integration.rb
# be +nil+,
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
+ # - headers_or_env: Additional headers to pass, as a Hash. The headers will be
@dhh
dhh Jan 19, 2015 Member

Let's just call this headers. Hate that "or" setup. The headers get turned into env, that's fine. Don't need to spell that out further.

Actually, even better, let's do both, and just merge them together. Then it'll be even clearer in the code what the intent is.

@rafaelfranca rafaelfranca commented on an outdated diff Jan 20, 2015
actionpack/lib/action_dispatch/testing/integration.rb
@@ -68,11 +75,31 @@ def head(path, parameters = nil, headers_or_env = nil)
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
# string; the headers are a hash.
- def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
- headers_or_env ||= {}
- headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- process(request_method, path, parameters, headers_or_env)
+ #
+ # Example:
+ #
+ # xhr :get, '/feed', params: { since: 201501011400 }
+ def xml_http_request(request_method, path, *args)
+ if kwarg_request?(*args)
+ # TODO slice
@rafaelfranca
rafaelfranca Jan 20, 2015 Member

This TODO need to be solved

@rafaelfranca
Member

Should we update the testing guide?

@kirs
Contributor
kirs commented Jan 20, 2015

@rafaelfranca TODO is fixed, thanks! I will update the guide soon.

@kirs
Contributor
kirs commented Jan 20, 2015

@rafaelfranca guide updated.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Jan 20, 2015
actionpack/lib/action_controller/test_case.rb
#
- # - +action+: The controller action to call.
- # - +parameters+: The HTTP parameters that you want to pass. This may
- # be +nil+, a hash, or a string that is appropriately encoded
+ # - action: The controller action to call.
+ # - params: The hash with HTTP parameters that you want to pass. This may be +nil+.
+ # - body: The request body with a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
@rafaelfranca
rafaelfranca Jan 20, 2015 Member

If we removed the fixed size of fonts of the other arguments we should do the same with session and flash.

@rafaelfranca
rafaelfranca Jan 20, 2015 Member

But I really not sure if we should remove it @fxn?

@kirs
kirs Jan 21, 2015 Contributor

What do you mean under "fixed size of fonts"?

@rafaelfranca
rafaelfranca Jan 21, 2015 Member

The +action+ over action.

@fxn
fxn Jan 21, 2015 Member

Yep, we should keep it.

@fxn
fxn Jan 21, 2015 Member

@kirs when you refer to variables, keyword arguments, etc. you use fixed-width font for them. That is assigns, rather than just assigns. Have a look at the API guidelines.

@kirs
kirs Jan 21, 2015 Contributor

Thanks for review and explanation, I will fix it.

@kirs
Contributor
kirs commented Jan 24, 2015

@rafaelfranca I added the CHANGELOG entry. Ready for merge?

@robin850 robin850 commented on an outdated diff Jan 25, 2015
actionpack/lib/action_controller/test_case.rb
@@ -491,58 +491,64 @@ def determine_default_controller_class(name)
end
end
- # Simulate a GET request with the given parameters.
+ # Simulate a GET request with the given keyword arguments.
@robin850
robin850 Jan 25, 2015 Member

"parameters" is maybe a better fit here ; "keyword arguments" is more an implementation detail and we are still passing the action as a normal parameter, not as a keyword argument. What do you think ?

@robin850 robin850 commented on an outdated diff Jan 25, 2015
actionpack/lib/action_controller/test_case.rb
#
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
# +post+, +patch+, +put+, +delete+, and +head+.
+ # Example sending parameters, session and setting a flash message:
+ #
+ # get :show
@robin850
robin850 Jan 25, 2015 Member

There's a missing comma here.

@robin850 robin850 commented on an outdated diff Jan 25, 2015
actionpack/lib/action_controller/test_case.rb
@@ -567,40 +573,68 @@ def paramify_values(hash_or_array_or_value)
#
# - +action+: The controller action to call.
# - +http_method+: Request method used to send the http request. Possible values
@robin850
robin850 Jan 25, 2015 Member

It should be +method:+, no ? Also a nit-picky thing but maybe we can use "HTTP" instead of "http".

@robin850 robin850 commented on an outdated diff Jan 25, 2015
actionpack/lib/action_controller/test_case.rb
#
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
# prefer using #get, #post, #patch, #put, #delete and #head methods
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
- def process(action, http_method = 'GET', *args)
+
@robin850 robin850 commented on an outdated diff Jan 25, 2015
actionpack/lib/action_dispatch/testing/integration.rb
@@ -12,12 +12,14 @@ module RequestHelpers
#
# - +path+: The URI (as a String) on which you want to perform a GET
# request.
- # - +parameters+: The HTTP parameters that you want to pass. This may
+ # - +parameters:+ The HTTP parameters that you want to pass. This may
@robin850
robin850 Jan 25, 2015 Member

It should be +params:+, no ?

@robin850 robin850 commented on an outdated diff Jan 25, 2015
actionpack/lib/action_dispatch/testing/integration.rb
@@ -89,40 +113,55 @@ def follow_redirect!
# redirect. Note that the redirects are followed until the response is
# not a redirect--this means you may run into an infinite loop if your
# redirect loops back to itself.
- def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
- process(http_method, path, parameters, headers_or_env)
+ #
+ # Example:
+ #
+ # request_via_redirect :post, '/welcome',
+ # params: { ref_id: 14 },
+ # headers: {"X-Test-Header" => "testvalue"}
+ def request_via_redirect(http_method, path, *args)
+ if kwarg_request?(*args)
@robin850
robin850 Jan 25, 2015 Member

It looks like you are duplicating some logic from process_with_kwargs, is it intended ? Otherwise, the method could look like:

def request_via_redirect
  process_with_kwargs(http_method, path, *args)

  follow_redirect! while redirect?
  status
end
@robin850 robin850 commented on an outdated diff Jan 25, 2015
guides/source/testing.md
@@ -481,20 +481,22 @@ In the `test_should_get_index` test, Rails simulates a request on the action cal
The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
* The action of the controller you are requesting. This can be in the form of a string or a symbol.
-* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables).
-* An optional hash of session variables to pass along with the request.
-* An optional hash of flash values.
+* `params:` option with a hash of request parameters to pass into the action (eg. query string parameters or article variables).
@robin850
robin850 Jan 25, 2015 Member

Missing dot after the "e" of "e.g.". Also could you please wrap your lines around 80 chars editing the guides ? That would be awesome!

@robin850 robin850 commented on an outdated diff Jan 25, 2015
guides/source/testing.md
@@ -481,20 +481,22 @@ In the `test_should_get_index` test, Rails simulates a request on the action cal
The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
* The action of the controller you are requesting. This can be in the form of a string or a symbol.
-* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables).
-* An optional hash of session variables to pass along with the request.
-* An optional hash of flash values.
+* `params:` option with a hash of request parameters to pass into the action (eg. query string parameters or article variables).
+* `session:` option with a hash of session variables to pass along with the request.
+* `flash` option with a hash of flash values.
@robin850
robin850 Jan 25, 2015 Member

Nit-picky missing colon. 😄

@robin850 robin850 commented on an outdated diff Jan 25, 2015
guides/source/testing.md
@@ -481,20 +481,22 @@ In the `test_should_get_index` test, Rails simulates a request on the action cal
The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
* The action of the controller you are requesting. This can be in the form of a string or a symbol.
-* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables).
-* An optional hash of session variables to pass along with the request.
-* An optional hash of flash values.
+* `params:` option with a hash of request parameters to pass into the action (eg. query string parameters or article variables).
+* `session:` option with a hash of session variables to pass along with the request.
+* `flash` option with a hash of flash values.
+
+All the key arguments are optional.
@robin850
robin850 Jan 25, 2015 Member

s/key/keyword

@robin850
Member

Awesome work @kirs !

There's just a tiny missing change here to avoid displaying a deprecation warning running the test suite. Also, regarding deprecation warnings, you may want to use strip_heredoc instead of squish to keep the formatting. At the moment, it displays like this:

DEPRECATION WARNING: ActionDispatch::Integration::TestCase HTTP request methods will accept only keyword arguments in future Rails versions. Examples: get '/profile', params: { id: 1 }, headers: { 'X-Extra-Header' => '123' }, env: { 'action_dispatch.custom' => 'custom' } xhr :post, '/profile', params: { id: 1 }. (called from block (3 levels) in run at ...).

But well, that's just styling concerns, there's no big deal. Thank you !

@robin850 robin850 added this to the 5.0.0 milestone Jan 25, 2015
@kirs
Contributor
kirs commented Jan 25, 2015

@robin850 thanks for review! You're also right about squish in deprecations. Will be fixed soon.

@kirs
Contributor
kirs commented Jan 25, 2015

@robin850 fixed 🚀

@kirs kirs Switch to kwargs in ActionController::TestCase and ActionDispatch::In…
…tegration

Non-kwargs requests are deprecated now.
Guides are updated as well.

`post url, nil, nil, { a: 'b' }` doesn't make sense.
`post url, params: { y: x }, session: { a: 'b' }` would be an explicit way to do the same
baf14ae
@kirs
Contributor
kirs commented Jan 29, 2015

Rebased one more time. ping @dhh @rafaelfranca @robin850

@rafaelfranca rafaelfranca merged commit baf14ae into rails:master Jan 29, 2015

1 check passed

continuous-integration/travis-ci The Travis CI build passed
Details
@rafaelfranca
Member

Awesome work @kirs

@senny senny added a commit that referenced this pull request Jan 30, 2015
@senny senny scaffold controller_test template should use kwargs. refs #18323.
This prevents a flood of warnings when generating a new scaffold.
670ac73
@dchacke
dchacke commented Dec 16, 2015

👍

@rafaelfranca rafaelfranca modified the milestone: 5.0.0 [temp], 5.0.0 Dec 30, 2015
@maclover7 maclover7 added a commit to maclover7/rails that referenced this pull request Apr 20, 2016
@maclover7 maclover7 Add #18323 to 5.0 release notes
Fixes #23643.

[ci skip]
b2238b5
@maclover7 maclover7 added a commit to maclover7/rails that referenced this pull request Apr 20, 2016
@maclover7 maclover7 Add #18323 to 5.0 release notes
Fixes #23643.

[ci skip]
820e38c
@maclover7 maclover7 added a commit to maclover7/rails that referenced this pull request Apr 20, 2016
@maclover7 maclover7 Add #18323 to 5.0 release notes
Fixes #23643.

[ci skip]
342a0de
@gkop
Contributor
gkop commented May 7, 2016 edited

This is awesome work folks. I just wanted to point out to those struggling to make the new API work and get rid of the deprecation warnings (with ActionDispatch::IntegrationTest in my case) that you can pass a raw payload as a value to the :params named argument, it doesn't have to be a hash. Also there seems to be some logical branching going on whether or not you are using the old API or the new API. So don't expect eg. the :headers named argument (or, ahem, hash key ;) ) to be respected until you make the rest of your method call conform to the new API.

@kirs
Contributor
kirs commented May 8, 2016

@gkop thank you for suggestions! Do you mean that it would make sense to check if params and headers keys are hashes?

@gkop
Contributor
gkop commented May 8, 2016 edited

The most confusing thing about the new API is it wants you to pass eg. a POST request raw body as the value of the :params named argument; I would expect to pass the body as :body or, if the named argument accepts either a raw or structured body (like :params does currently), perhaps :payload.

The remaining confusion is only while supporting the old API and new API, you can't "mix and match", eg. send a raw string as the second argument and then a headers hash keyed on :headers in the options hash; that headers hash is then silently ignored. I'm not sure it's worth adding complexity to either respect the special values in the options hash or add a warning message if they're detected - I think good documentation of the new API (including covering how to send a raw POST body!) will mostly alleviate this confusion.

@dteoh
Contributor
dteoh commented Jul 7, 2016

I don't suppose there is a way to preserve the types of objects when receiving a request?

In Rails 4.2:

put :update, obj: { attr_1: nil, attr_2: false }

the controller's params[:obj] will be as-is, i.e. { attr_1: nil, attr_2: false }.

After updating to Rails 5 and fixing deprecations:

put :update, params: { obj: { attr_1: nil, attr_2: false } }

the controller's params[:obj] will be { attr_1: "", attr_2: "false" }, i.e. every value is turned into a String.

@kirs
Contributor
kirs commented Jul 7, 2016

Douglas, AFAIK that's expected Rack behaviour because HTTP parameters are
always strings.

On Jul 7, 2016 03:23, "Douglas Teoh" notifications@github.com wrote:

I don't suppose there is a way to preserve the types of objects when
receiving a request?

In Rails 4.2:

put :update, obj: { attr_1: nil, attr_2: false }

the controller's params[:obj] will be as-is, i.e. { attr_1: nil, attr_2:
false }.

After updating to Rails 5 and fixing deprecations:

put :update, params: { obj: { attr_1: nil, attr_2: false } }

the controller's params[:obj] will be { attr_1: "", attr_2: "false" },
i.e. every value is turned into a String.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#18323 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/AAf3q-CTFDTzaZznCl7VHF659nWIyP-Cks5qTKl4gaJpZM4DOGBH
.

@dteoh
Contributor
dteoh commented Jul 8, 2016

Fair enough. I expect that this change is going to break a lot of tests. I've got an API that deals with JSON request bodies. This new test class assumes only form encoded values and there is no public API to coerce the framework into interpreting String bodies as JSON. The :headers key as described in the docs currently do nothing.

I'm trying the following in a test, and looking at request.headers in the controller and see no evidence of the custom or overridden headers being passed along. Kinda confused about whether the tests are running through the full Rack + Rails stack or just some portions of it.

put :update, params: { obj: { attr_1: nil, attr_2: false } }, headers: { 'X_CUSTOM' => 'foobar', 'X-Custom' => 'something', 'CONTENT_TYPE' => 'application/json'  } 
@arthurkulchenko arthurkulchenko referenced this pull request in arthurkulchenko/qna_think Jul 13, 2016
Closed

Acceptance #3

@pawandubey

I have the same problem as @dteoh above. Even when specifying the format: :json the values in the params hash are converted to string. This has several hundreds of our specs failing right now.

@dteoh
Contributor
dteoh commented Jul 19, 2016

@pawandubey I just bit the bullet and switched over to ActionDispatch::IntegrationTest to make the tests work again. I added a few helper methods to the class to make the transition, and things work again.

Helper methods:

https://gist.github.com/dteoh/2d4c115446e2429824b6945c45c07f3b

@pawandubey

@dteoh Thanks, that was helpful, but I would like to keep using Rspec and achieve the same behavior(as it should be by default since rspec-rails is just a thin wrapper over the Rails helpers).

@connorshea
Contributor

Anyone having trouble with this change specifically may want to take a look at the rails5-spec-converter gem

@connorshea connorshea referenced this pull request in helpyio/helpy Jul 20, 2016
Open

Rails 5 #224

@trejkaz
trejkaz commented Aug 2, 2016

This warning was awfully hard to understand because by reading my code, it seemed like my params were already "keyword arguments". Now I understand that what it really means for all of my usages is that params can no longer be passed bare anymore.

@aaronrussell

Is there a way of writing ActionController::TestCase tests that will pass in both Rails 5 and 4.2? (for rails engines that need to run in both)

@connorshea
Contributor

@aaronrussell they'll still pass in both, but will print deprecation warnings on 5.

@kaspth kaspth added a commit that referenced this pull request Aug 13, 2016
@kaspth kaspth Clarify xhr deprecation message. Don't support kwargs.
Prevent hitting integration tests users with two deprecation warnings by
clarifying how they should upgrade to the request helpers `get` and friends.

It's hard to imagine people having `xhr` calls that use keywords, why not
follow the xhr deprecation message? Why instead insert a middle layer of
migration?

They have to switch to something else anyway, so just show how that looks
and nudge them a bit more.

The code was originally added in #18323,
but then wasn't touched when deprecating xhr in
#18771.
258f4e7
@edwardloveall edwardloveall added a commit to edwardloveall/pullfeed that referenced this pull request Nov 2, 2016
@edwardloveall edwardloveall Add keyword arguments to integration/controller tests
rails/rails#18323/

Changes code like:

* get :show, params.merge(format: :atom)
* get(path, {}, headers)

to

* get :show, params: params.merge(format: :atom)
* get(path, params: {}, headers: headers)

Thanks to @rosmith76 and @asackofwheat
9f04327
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment