11

Looks like a bug in RSpec but maybe I'm missing something.

I have a request spec where I post a JSON that contains an array of hashes:

spec/requests/dummy_request_spec.rb:

post "http://my.server.com/some/route", {
  format: :json,
  data: [
    {
      details: {
        param1: 1
      },
    },
    {
      details: {
        param2: 1
      }
    }
  ]
}

For some odd reason, RSpec merges the hashes into one element and then sends them to server. print out of params received in controller:

data: [
  {
    details: {
      param1: 1,
      param2: 2
    },
  },
]

versions: rspec-2.13.0 rails-3.2.10

Very strange!!

Thanks

3 Answers 3

21

Got it! array of hashes is not supported for form-data
RSpec by default posts it as form-data. Solution:

post '...', {...}.to_json, {'CONTENT_TYPE' => "application/json", 'ACCEPT' => 'application/json'}
Sign up to request clarification or add additional context in comments.

1 Comment

Side note: array of hashes for form-data is supported, but requires 'index' field, otherwise entities will be merged into one. This may be useful if you want to send array of files.
1

I faced the same problem reported in the question post while using following versions

ruby 2.3.2

rails (5.0.0.1)

rspec-rails (3.5.2)

Searching for the problem on web I found a related issue at https://github.com/rails/rails/issues/26069 and the solution suggested by it is to pass as: :json option to the post, get etc methods while using them in the controller tests (refer the PR referenced in comment https://github.com/rails/rails/issues/26069#issuecomment-240916233 for more details). Using that solution didn't solved the params mingling issue I was encountering. Following were the results found for different types of data I used with the recommended solution:

In my controller spec I have following

before(:each) do
 request.accept = "application/json"
end

and in the test logs I do see that the request is being served

Processing by Api::V1::MyController#my_action as JSON

Data-1

data = [
  {
    param_1: "param_1_value",
  },
  {
    param_2: "param_2_value",
  }
]

params.merge!(my_data: data)

post :my_action, params: params, as: :json

That ends-up in request params as following

{ "my_data"=> [ {"param_1"=>"param_1_value", "param_2"=>"param_2_value"} ] }

which is wrong.

Data-2

data = [
  {
    param_1: "param_1_value",
    something_else: ""
  },
  {
    param_2: "param_2_value",
    another_thing: ""
  }
]

params.merge!(my_data: data)

post :my_action, params: params, as: :json

That ends-up in request params as following

{ "my_data"=> [ {"param_1"=>"param_1_value", "something_else"=>"", "another_thing"=>"", "param_2"=>"param_2_value"} ] }

which is wrong.

Data-3

data = [
  {
    param_1: "param_1_value",
    param_2: ""
  },
  {
    param_1: ""
    param_2: "param_2_value",
  }
]

params.merge!(my_data: data)

post :my_action, params: params, as: :json

That ends-up in request params as following

{ "my_data"=>[ {"param_1"=>"param_1_value", "param_2"=>""}, {"param_1"=>"", "param_2"=>"param_2_value"} ] }

which is correct.

It should be noted that for Data-3 without the as: :json option also I receive the correct data in request params.

One more thing: In comment https://github.com/rails/rails/issues/26069#issuecomment-240358290 an alternate solution suggested to deal with the problem narrated above is following

another fix would be to specify nested attributes not as array but as hash:

params = {
      id: @book.id,
      book: {
        title: 'Cool',
        pages_params: {
          "0" => { id: @page.id, content: 'another content' },
          "1" => { id: @delete_page.id, _destroy: 1 },
          "2" => { content: 'another new page' }
        }
      },
      format: :json
    }

Unfortunately this was removed from the documentation of nested attributes so I don't know if this is going to stay valid. http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

But this solution has a drawback is that we need to manually sanitize the data on controller-end to bring it back to expected structure i.e. Array of Hashes.

Finally I am sharing below what worked for me

spec/shared_contexts.rb

RSpec.shared_context "common helpers" do

  def set_request_header(request_obj:, request_header_name:, request_header_value:)
    request_obj.headers[request_header_name] = request_header_value
  end

  def set_request_header_content_type_as_json(request_obj:)
    set_request_header(request_obj: request_obj, request_header_name: 'CONTENT_TYPE', request_header_value: 'application/json')
  end
end

Then in my spec file

require 'shared_contexts'

RSpec.describe Api::V1::MyController, :type => :controller do
  include_context "common helpers"

  context "POST #my_action" do  
    it "my example" do
      data = [
        {
          param_1: "param_1_value",
        },
        {
          param_2: "param_2_value",
        }
      ]

      params.merge!(my_data: data)

      set_request_header_content_type_as_json(request_obj: request)

      post :my_action, params: params
    end
  end 
end

As can be seen setting the request header CONTENT_TYPE was what was missing to make the request params to be received in expected structure.

Comments

0

Also, be aware that you have an extra comma:

data: [
  {
   details: {
    param1: 1
   }**,**
  },
  {
   details: {
    param2: 1
   }
  }
 ]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.