16

I plan to use JSON data in both request and response in my project and having some problems in testing.

After searching for a while, I find the following code which uses curl to post JSON data:

curl -H "Content-Type:application/json" -H "Accept:application/json" \
    -d '{ "foo" : "bar" }' localhost:3000/api/new

In the controller I can access the JSON data simply using params[:foo] which is really easy. But for functional testing, I only find post and xhr (alias for xml_http_request).

How can I write functional test in rails to achieve the same effect as using curl? Or should I do test in other ways?

Here's what I've tried. I find the implementation for xhr in action_controller/test_case.rb, and tried to add jhr method simply changing 'Conetent-Type' and 'HTTP_ACCEPT'. (Added in test/test_helpers.rb.)

def json_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
  @request.env['Content-Type'] = 'Application/json'
  @request.env['HTTP_ACCEPT'] ||= [Mime::JSON, Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
  __send__(request_method, action, parameters, session, flash).tap do
    @request.env.delete 'Content-Type'
    @request.env.delete 'HTTP_ACCEPT'
  end
end
alias jhr :json_http_request

I used this in the same way as xhr, but it does not work. I inspected the @response object and sees the body is " ".

I also find one similar question on Stack Overflow but it's for rails 2 and the answer for posting raw data does not work in rails 3.

Community
  • 1
  • 1
cyfdecyf
  • 816
  • 2
  • 10
  • 20
  • 1
    Write functional tests as usual, just specify request format, like post :new, :foo => 'bar', :format => 'json' – taro Apr 30 '11 at 13:16
  • I tried. But the response's content-type is "text/html" and body is `" "`. When using ``xhr`` to do post, the response's content-type is "application/json" and body is JSON data. – cyfdecyf May 01 '11 at 15:17

7 Answers7

24

As of Rails 5, the way to do this is:

post new_widget_url, as: :json, params: { foo: "bar" }

This will also set the Content-type header correctly (to application/json).

sandstrom
  • 14,554
  • 7
  • 65
  • 62
Paul Cantrell
  • 9,175
  • 2
  • 40
  • 48
  • This is the new correct answer for Rails 5. The old way gives `DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only the following keyword arguments in future Rails versions: params, headers, env, xhr, as ` – zmanw Sep 28 '16 at 15:04
  • The request code can be found in _actionpack-5.0.0.1/lib/action_dispatch/testing/integration.rb_ line 326 method `process`. But the [doc](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html#method-i-get) does not mention the `as` parameter – Benjamin Bouchet Nov 01 '16 at 14:54
10

I found that this does exactly what I want – post JSON to a controller's action.

post :create, {:format => 'json', :user => { :email => "test@test.com", :password => "foobar"}}
Sebastian Wramba
  • 10,087
  • 8
  • 41
  • 58
9

Just specify appropriate content type:

post :index, '{"foo":"bar", "bool":true}', "CONTENT_TYPE" => 'application/json'

Json data should go as a string, not as a Hash. Looking at stack trace running a test you can acquire more control on request preparation: ActionDispatch::Integration::RequestHelpers.post => ActionDispatch::Integration::Session.process => Rack::Test::Session.env_for

Specifying :format does not work because request go as 'application/x-www-form-urlencoded' and json isn't parsed properly processing a request body.

Grimmo
  • 1,485
  • 14
  • 13
  • 7
    Directly posing the string will get the following error ``NoMethodError: undefined method `symbolize_keys' for "{ \"foo\" : \"bar\" }":String`` – cyfdecyf Dec 17 '11 at 10:51
  • I use rack-test gem to do such kind of testing also because it simplifies authentication for many requests. Add 'require "rack/test"' at the top of your scenario file. – Grimmo Dec 22 '11 at 12:56
  • This fixes it for me. Pretty odd though that this is necessary with Rails... seems like a Rails bug. – GregT Jan 30 '13 at 02:08
  • I voted this down because the question specifically asks about a functional test and as best as I can tell CONTENT_TYPE is only respected if you are using an IntegrationTest – chrisortman Feb 04 '15 at 15:04
4

Assuming you have a controller named api, a method named new, and you're in the test for the api controller:

@request.env["RAW_POST_DATA"] = '{ "foo" : "bar" }'
post :new

did the trick for me.

brokenbeatnik
  • 720
  • 6
  • 15
1

Here is a snippet that let me post json data to test my own app. rails 3

port = Rails.env.production? ? 80 : 3000
uri = URI.parse( Rails.application.routes.url_helpers.books_url(:host => request.host, :port => port, :format => :json) )
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request.content_type = 'application/json'
request.body = @json_data
response = http.request( request )
@result = response.body

Hope this helps others

genkilabs
  • 2,966
  • 30
  • 36
0

As @taro suggests in a comment above, the syntax that works for me in functional and integration tests is:

post :create, {param1: 'value1', param2: 'value2', format: 'json'}

(The curly braces aren't always necessary, but sometimes it doesn't work if they're missing, so I always add them.)

Here's what params and request.format look like for a post of that sort:

params: {"param1"=>"value1", "param2"=>"value2", "format"=>"json", "controller"=>"things", "action"=>"create"}

request.format: application/json

Jason Heiss
  • 671
  • 6
  • 11
0

The best answer I can come up with to this is you don't

Whether or not it was intentional it s maybe good that rails doesn't implement this for you.

In functional tests you really want to just test your controller and not rails method of deserialization or even that routing and mime detection are all setup correctly, those all fall under an IntegrationTest.

So for your controllers, don't pass JSON just pass your params hash like you normally would. Maybe adding :format as an argument as well if you need to check that and respond differently.

If you want to test the full stack move to an IntegrationTest

chrisortman
  • 1,514
  • 15
  • 22