How to send raw post data in a Rails functional test?

Viewed 34867

I'm looking to send raw post data (e.g. unparamaterized JSON) to one of my controllers for testing:

class LegacyOrderUpdateControllerTest < ActionController::TestCase
  test "sending json" do
    post :index, '{"foo":"bar", "bool":true}'
  end
end

but this gives me a NoMethodError: undefined method `symbolize_keys' for #<String:0x00000102cb6080> error.

What is the correct way to send raw post data in ActionController::TestCase?

Here is some controller code:

def index
  post_data = request.body.read
  req = JSON.parse(post_data)
end
13 Answers

I ran across the same issue today and found a solution.

In your test_helper.rb define the following method inside of ActiveSupport::TestCase:

def raw_post(action, params, body)
  @request.env['RAW_POST_DATA'] = body
  response = post(action, params)
  @request.env.delete('RAW_POST_DATA')
  response
end

In your functional test, use it just like the post method but pass the raw post body as the third argument.

class LegacyOrderUpdateControllerTest < ActionController::TestCase
  test "sending json" do
    raw_post :index, {}, {:foo => "bar", :bool => true}.to_json
  end
end

I tested this on Rails 2.3.4 when reading the raw post body using

request.raw_post

instead of

request.body.read

If you look at the source code you'll see that raw_post just wraps request.body.read with a check for this RAW_POST_DATA in the request env hash.

I actually solved the same issues just adding one line before simulating the rspec post request. What you do is to populate the "RAW_POST_DATA". I tried to remove the attributes var on the post :create, but if I do so, it do not find the action.

Here my solution.

def do_create(attributes)
  request.env['RAW_POST_DATA'] = attributes.to_json
  post :create, attributes
end 

In the controller the code you need to read the JSON is something similar to this

  @property = Property.new(JSON.parse(request.body.read))

I was searching very long for how to post raw JSON content in a integration test (Rails 5.1). I guess my solution could also help in this case. I looked up the documentation and source code for the post method: https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/RequestHelpers.html#method-i-post

This directed me to the process method for more details: https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Integration/Session.html#method-i-process

Thanks to this, I finally found out what parameters are accepted by the process and thus post method. Here's what my final solution looked like:

post my_url, params: nil, headers: nil, env: {'RAW_POST_DATA' => my_body_content}, as: :json

In rails, 5.1 the following work for me when doing a delete request that needed data in the body:

delete your_app_url, as: :json, env: {
   "RAW_POST_DATA" =>  {"a_key" => "a_value"}.to_json
}

NOTE: This only works when doing an Integration test.

The post method expects a hash of name-value pairs, so you'll need to do something like this:

post :index, :data => '{"foo":"bar", "bool":true}'

Then, in your controller, get the data to be parsed like this:

post_data = params[:data]

In Rails 4 (at least in 4.2.11.3) there's no easy way to test your controllers that consume json (functional tests). For parsing json in a running server the ActionDispatch::ParamsParser middleware is responsible. Controller tests though rely on Rack, which can't parse json to this day (not that it should).

You can do:

post :create, body_params.to_json

or:

post :update, body_parmas.to_json, url_params

But body_params won't be accessible in the controller via params. You've got to do JSON.parse(request.body.read). So the only thing that comes to mind is:

post :update, url_params.merge(body_params)

That is, in tests pass everything via parameters (application/x-www-form-urlencoded). In production the body will be parsed by ActionDispatch::ParamsParser to the same effect. Except that your numbers become strings (and possibly more):

# test/controllers/post_controller_test.rb
post :update, {id: 1, n: 2}

# app/controller/posts_controller.rb
def update
    p params  # tests:
              # {"id"=>"1", "n" => "2", "controller"=>"posts", "action"=>"update"}
              # production
              # {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
end

If you're willing to parse json in controllers yourself though you can do:

# test/controllers/post_controller_test.rb
post_json :update, {n: 2}.to_json, {id: 1}

# app/controller/posts_controller.rb
def update
    p JSON.parse(request.body.read)  # {"id"=>"1", "n" => 2, "controller"=>"posts", "action"=>"update"}
end
post :index, {:foo=> 'bar', :bool => 'true'}
Related