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...
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.
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)
...
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.