rspec / rspec-rails

RSpec for Rails 6+
https://rspec.info
MIT License
5.18k stars 1.04k forks source link

Nested parameters in controller tests do not parse correctly #1700

Closed cvoege closed 7 years ago

cvoege commented 8 years ago

So I have a method that takes some nested JSON data as parameters. If I pass this JSON data into my controller as the body of an AJAX request from my application, it parses just fine and I can use the parameters as normal. However, if I use this exact same data as the params: {} of an rspec field, they don't parse right, some data from the third element of the array gets moved into the second element!

I've simplified the data to give you guys an easy to test use case.

Here's a basic spec file to start with, the JSON data in question can be seen in the params of the create test.

describe QuestionsController do
  describe 'post #create' do
    subject do
      post :create, format: :json, params: {
        questions: [
          {
            prompt: '1',
            details: {}
          },
          {
            prompt: '2',
            details: {}
          },
          {
            prompt: 'Should have no choices, but for some reason they get pushed into question 2!',
            details: {
              answer: 1,
              choices: [
                'Choice 1',
                'Correct Choice',
                'Another Choice'
              ]
            }
          }
        ]
      }
    end
    context 'as authorized user' do
      it 'should be success' do 
        expect(subject).to be_success
      end
    end
  end

And for the controller you can simply print out the params:

class QuestionsController < ApplicationController
  def create 
    p params
  end
end

And here's how the parameters come out looking when you print them:

<ActionController::Parameters {"questions"=>[{"prompt"=>"1"}, {"prompt"=>"2", "details"=>{"answer"=>"1", "choices"=>["Choice 1", "Correct Choice", "Another Choice"]}}, {"prompt"=>"Should have choices, but for some reason they get pushed into question 2!"}], "format"=>"json", "controller"=>"recruiting/interviews", "action"=>"create"} permitted: false>

And you'll notice they get mangled and for some reason Question 3's details get put into question 2! I tried to work around this by passing the data as a JSON string in the body of a post call, but I can't find any documentation on how to do that in the most recent version, and anything I tried still resulted in this bug. I'd like to again note that I can pass this data exactly into the app without any problems when it's not in a test.

fables-tales commented 8 years ago

@cvoege is this a new bug since Rails 5 or has this always been the case for you?

cvoege commented 8 years ago

@samphippen Hard to tell, since I didn't write this test til I switched to rails 5.

fables-tales commented 8 years ago

@cvoege could you follow our instructions for building an app which cleanly demonstrates the issue you're having please:

Thanks for the issue. We need a little more detail to be able to reproduce this.

Could you please provide us with a rails app that we can clone that demonstrates the issue. Specifically it'd be great if

1) you could rails new an application and commit 2) make all the changes necessary to reproduce the issue and commit

then, provide us with a description of how to clone your application and reproduce the issue.

Thanks :)

cvoege commented 8 years ago

@samphippen Here you go: https://github.com/cvoege/rspec-params-issue

Also, another issue worth looking at in this same vein.

It seems that in rspec the params object is converted into a query string, and improperly doing so appears to be what causes this bug, but it also causes some other issues like integers and booleans get converted into strings. This works for url-encoded-forms and multipart forms, but if you post JSON as the body of a request you integers and booleans will stay proper integers and booleans.

For example, the following params:

params: {
  test1: 5,
  test2: false,
  test3: 'test'
}

Would convert to:

{
  test1: "5",
  test2: "false",
 test3: "test"
}

As ActionController::Params. This is all well and good for most cases, but again, if we're posting a JSON body it ends up different. So I feel like part of the solution here needs to be to allow a way for us to pass a JSON string as the body of the request.

tosch commented 8 years ago

@cvoege Can you try using as: :json instead of format: json? This should ensure the params get encoded as JSON. See http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html

tosch commented 8 years ago

@cvoege Sorry, that won't work. The IntegrationTest is used for request specs. With type: :request and post '/questions', as: :json I can get your specs running (but please note that the post does not return the response in that case, request specs are different from raw controller specs.

The issue is, in my opinion, not within rspec-rails, but ActionController::TestCase::Behavior, which does not support JSON payload very well, as it seems.

cvoege commented 8 years ago

@tosch Thank you for your input. I'll try that out. If I were to report the ActionController::TestCase bug, would that be in the rails repo itself?

orlando commented 8 years ago

I was looking at this issue as part of Hacktoberfest, https://github.com/rails/rails/commit/3db57bde1ef435a38337f6db298b851c8a4ad10c fixes this. The only thing is that you need to use as instead of format and it should work correctly. I don't know exactly what's the difference between these two is supposed to be, but the code fix only addresses as https://github.com/rails/rails/commit/3db57bde1ef435a38337f6db298b851c8a4ad10c#diff-600d5368b55e46ed961abb4295977ac3R519

I made a fork (https://github.com/orlando/rspec-params-issue) of the reproduction case and:

so, in short, if you use as instead of format using the rails from master it works correctly

This also closes #710

mrageh commented 7 years ago

@samphippen I think we can close this issue, refer to @orlando comment above for more info :)

emaillenin commented 7 years ago

Wow never thought this could be an issue with Rails 5. Spent a day trying to fix a test and finally found this. For anyone with this issue, try this:

post :create, params: your_post_payload_hash, as: :json
arthurmde commented 7 years ago

Thank you @emaillenin . Your answer helped me with a similar issue. Actually, Rails seems to have an open issue related to this problem -> https://github.com/rails/rails/issues/23997

letiesperon commented 4 years ago

What is the workaround for tests that also upload files for their nested attributes? as: :json doesn't help on that case since it has to be multipart. Any ideas?

JonRowe commented 4 years ago

You'll need the correct mime type for the json/multipart hybrid, what rails will treat it as. This is all being passed through to the rails test helpers.