How to Specify Post Params in a Rails Test

How do you specify POST params in a Rails test?

post :create, :user => { :email => 'foo@bar.com' }

The general form for all the test methods of get, post, put, delete are as follows:

def post(action_name, params_hash = {}, session_hash = {})

And in tests, the params hash gets directly sent into params of your controller action with no translation of any sort. Even doing integration testing you really shouldnt need to test this string to params translation as its covered very well by the rails framework tests. Plus all testing methods that need params accept a hash in this manner without complaint making things easy for you.

Rails functional test: sending URL query parameters in POST request

A little background first to clarify things: although a request cannot be both GET and POST at the same time, there is nothing stopping you from using both the query string and body form data when using POST. You can even have a POST with all parameters in the query string and an empty body, though this sounds quite unusual.

Rails supports this scenario and indeed you can easily send a form using a POST request and still have query in the form's action. The query will be accessible with request.GET hash (which is an alias of query_string), while the POST body params with the request.POST hash (an alias of request_parameters). The params hash is actually constructed from the combined GET and POST hashes.

However, from my research it seems that Rails does not support passing query string in POST requests in functional controller tests. Although I could not find anything regarding this in any documentation or among known issues on github, the source code is quite clear. In the following text, I'm assuming that you use Rails 4.

Why it does not work

The problem with functional controller tests is that they don't use real requests / responses but they simulate the HTTP handshake: the request is mocked up, its parameters filled in appropriate places and the given controller action is simply called as a normal ruby method. All of this is done in the action_controller/test_case classes.

As it turns out, this simulation is not working in your particular case, due to two reasons:

  1. The parameters passed in when running the test are always handed over either to the request_parameters, i.e. the request.POST hash when using a post request or to the query_string (i.e. request.GET) for get test requests. There is no way for both of these hashes to be set during a single test run.

    This actually makes some sense as the get, post, etc. helpers in functional tests accept only a single hash of params so the internal test code cannot know how to separate them into the two hashes.

  2. It is true that one can set up the request before running the test using the @request variable, but only to a certain extent, you can set headers, for example. But you cannot set internal attributes of the request, because they are recycled during the test run. The recycling is done here and it resets all internal variables of the request object and the underlying rack request object. So if you try to set up the request GET parameters like this @request.GET[:api_key] = 'my key', it won't have any effect as the internal variables representing this hash will get wiped during recycling.

Solutions / workarounds

  • Give up functional testing and choose integration tests instead. Integration tests allow to set the rack environment variables separately from the main parameters. The following integration test passes the QUERY_STRING rack env variable besides the normal post body params and should work flawlessly:

    class CollectionsTest < ActionDispatch::IntegrationTest
    test 'foo' do
    post collections_path, { collection: { name: 'New Collection' } },
    { "QUERY_STRING" => "api_key=my_api_key" }

    # this proves that the parameters are recognized separately in the controller
    # (you can test this in you controller as well as here in the test):
    puts request.POST.inspect
    # => {"collection"=>{"name"=>"New Collection"}}
    puts request.GET.inspect
    # => {"api_key"=>"my_api_key"}
    end
    end

    You can still use most of the features from functional tests in your integration tests. E.g. you can test for assigned instance variables in the controller with the assigns hash.

    The transition argument is supported also by the fact that Rails 5 will deprecate functional controller tests in favor of integration testing and since Rails 5.1 these functional tests support will be moved out to a separate gem.

  • Try Rails 5: although functional tests will be deprecated, its source code seems to have been heavily rewritten in the rails master and e.g. recycling of the request is not used any more. So you might give it a try and try to set the internal variables of the request during test setup. I have not tested it though.

  • Of course, you can always try to monkey-patch the functional test so that it supports separate params for the query_string and request_parameters hashes to be defined in tests.

I'd go the integration tests route :).

In Rails and minitest, how do I name my parameters when simulating a POST create request for my model?

The hash-keys in your params hash are not valid.

params: {rating[person_id]: person.id, rating[score]: score }

You can't use rating[person_id] and use the newer hash-syntax/ you will need to use hash-rockets here eg:

params: {rating[person_id] => person.id, rating[score] => score }

This is assuming you have a local variable called rating with the keys in it...
If you aren't actually trying to get a value out of a local variable called rating... then you may need to be more detailed about your structure eg:

params: { rating: { person_id: person.id, score: score } }

of course... depending on which kind of test you are writing, you might need to not use ratings_url (also try it with and without the params key) eg:

post :ratings, params: { rating: { person_id: person.id, score: score } }
post :ratings, rating: { person_id: person.id, score: score }

Rails 5 testing POST request

Here is the code that you were supposed to copy:

assert_no_difference 'User.count' do
post users_path, params: { user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end

The key-value pair params: some_hash is the second argument to post()--not the key-value pair user: some_hash.

The params hash is a pretty important concept in rails.

RSpec testing: post multiple params to methods

In RSpec controller and request specs you can simply pass arrays and hashes to create any given params hash.

Controller (functional) spec:

require 'rails_helper'
describe MailingsController do

let!(:receiver) { create(:receiver) }

describe 'POST :create' do
it "has the correct receivers" do
post :create, { mailing: { receivers: [receiver.id] } }
expect(Post.last.receivers).to eq [receiver]
end
end
end

Request (integration) spec:

require 'rails_helper'
describe 'Mailings' do
let!(:receiver) { create(:receiver) }
describe 'POST /mailings' do
it "has the correct receivers" do
post '/mailings', { mailing: { receivers: [receiver.id] } }
expect(Post.last.receivers).to eq [receiver]
end
end
end

Note however if you are using the rails collection helpers such as collection_checkboxes properly the param key should be receiver_ids.

Rails test post content

Raw post is request body, in modern rails you can pass a string for params parameter:

  post "/candy/user/profiles", headers: {authorization: @session[:authorization]}, params: 'something'

Rspec format Post parameters to String values

I added format json to post request and It worked like charm

before(:each) do
post :create, { bill: bill_attributes, format: :json }
end

Testing POST request to Posts controller

Well, it was indeed a confusion between the method and model names, though I wasn't looking far enough upstream to see it: my tests were set up as follows:

describe 'in the Posts controller' do
let(:post) { Post.create(...) }
.
.
.
describe 'attempting to issue a direct POST request while not signed in' do
before { post posts_path } # 'post' is interpreted to be the variable!
specify { expect(response).to redirect_to signin_path }
end
end

So, there was a naming conflict between the HTTP method POST and the variable 'post' declared at the beginning of the test block. Renaming the variable to 'test_post' fixed everything.

D'oh!

@DaveNewton: evidently, in cases where the POST request is rejected outright, it is possible to test it without parameters.



Related Topics



Leave a reply



Submit