Testing JSON API in Rails 3.2 using rspec using ex

2019-07-03 05:32发布

问题:

I'm trying to write a set of rspec tests for a JSON API written in Rails 3.2 with Backbone as the front end. The tests I'm writing are specifically for the Rails controllers.

Now, the application itself is working fine. When a client issues a PUT request with the body:

{
  "id":1,
  "name":"updated product set",
  ...
}

Everything is great. In the background Rails will take that body, then transform it into:

{
  "id":1,
  "name":"updated product set",
  ...
  "model_name" => { ... }
}

where the hash "model_name" points to contains the attribute values it can automatically figure out from the input. All is well.

When I try the same exact request from the testing environment, all is not well. If I create a hash that is exactly the same PUT body hash as above, but in rspec:

@update_json = {
  "id":1,
  "name":"updated product set",
  ...
}
header "Accept","application/json"
header "Content-type","application/json"
put :update, @update_json

Things do not work out at all. In the controller, if I inspect the params variable I get:

{ "model_name" => {} }

If I omit the "Content-type" header line, I get instead @update_json, but without the Rails manipulation that creates the "model_name" mapping which I require in my controller.

The only way I've gotten a successful test to run is the following:

@update_json = {
  :format => "json",
  "id":1,
  "model_name" => {
      "id":1,
      "name":"updated product set",
      ...
  }
}
header "Accept","application/json"
put :update, @update_json

However, this is not a real-world test since it's not testing the exact PUT body that would be sent by my backbone front-end.

Any ideas?

UPDATE: From looking around it looks like you can't actually do a real HTTP request from rspec; it's just mocked up. For example, you can't get the system to respond with a 404 since Rails throws an Exception that would normally be caught by Rails network stack and turned into a 404 but which isn't with Rspec. So the problem looks deeper than what's above...

回答1:

I was having the same problem. No matter what I did, or which ways I specified the content type as 'application/json', it just wouldn't work. If there was a suggestion on the internet for setting the content type to json, I tried it. I even tried them all together at the same time.

Ultimately, I traced it to ActionController::ParamsWrapper, where _wrapper_enabled? was always returning false (due to request.content_mime_type being nil). This solution works for me. I was looking for the 'magical' params key to get added as well and this does it.

mime_type = mock
mime_type.stub :ref => :json
request.stub :content_mime_type => mime_type
request.accept = 'application/json'
post :create, widget.as_json

The values are:

widget.as_json #=> {
  "created_at"=>nil,
  "description"=>"Description 1 - Quidem nihil quae aliquid sed qui.",
  "id"=>nil,
  "order"=>1,
  "title"=>"Title 1 - ut",
  "updated_at"=>nil
}

# params hash in the controller.
params #=> {
  "created_at"=>nil,
  "description"=>"Description 1 - Quidem nihil quae aliquid sed qui.",
  "id"=>nil,
  "order"=>"1",
  "title"=>"Title 1 - ut",
  "updated_at"=>nil,
  "controller"=>"api/widgets",
  "action"=>"create",
  "widget"=>{
    "title"=>"Title 1 - ut",
    "order"=>"1",
    "description"=>"Description 1 - Quidem nihil quae aliquid sed qui."
  }
}

This is exactly what I wanted. Only title, order, and description are set as attr_accessible, hence the only attributes that appear in the magically created :widget hash.

I am also using active_model_serializers, so the as_json is running through that, in case anyone thinks it matters.

This is part of a public example app I'm building, so this code will be able to be seen in use, if anyone thinks it might be useful.



回答2:

Can you try this?

@update_json = {
  "id":1,
  "model_name" => {
      "id":1,
      "name":"updated product set",
      ...
  }
}
put :update, @update_json, :format => :json

Edit (11/11/12):

Have you tried to instantiate the request manually and see what happens?

Weird. Have you tried to go the "plain Ruby way"?

env = Rack::MockRequest.env_for('/posts/1', :method => 'PUT', <some other params>)
status, headers, body = PostsController.action(:update).call(env)
...


回答3:

Looks like the ability to do post :update, '{"some": "json"}' was added to the internal ActionPack test_case.rb used by rspec in this commit: https://github.com/rails/rails/commit/5b9708840f4cc1d5414c64be43c5fc6b51d4ecbf

So, other options could be to upgrade to Rails 4 and things will work as expected, or to integrate the above commit.