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

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

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

Version for Rails 5:

post :create, body: '{"foo": "bar", "bool": true}'

See here - body string parameter is treated as raw request body.


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

You can pass json string as :params AND specify a content type "application/json". In other case content type will be set to "application/x-www-form-urlencoded" and your json will be parsed properly.

So all you need is to specify "CONTENT_TYPE":

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

For those using Rails5+ integration tests, the (undocumented) way to do this is to pass a string in the params argument, so:

post '/path', params: raw_body, headers: { 'Content-Type' => 'application/json' }