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

Ruby on-RailsJsonTesting

Ruby on-Rails Problem Overview


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

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

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.

Solution 2 - Ruby on-Rails

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

Solution 3 - Ruby on-Rails

Version for Rails 5:

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

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

Solution 4 - Ruby on-Rails

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'

Solution 5 - Ruby on-Rails

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' }

Solution 6 - Ruby on-Rails

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

Solution 7 - Ruby on-Rails

If you are using RSpec (>= 2.12.0) and writing Request specs, the module that is included is ActionDispatch::Integration::Runner. If you take a look at the source code you can notice that the post method calls process which accepts a rack_env parameter.

All this means that you can simply do the following in your spec:

#spec/requests/articles_spec.rb

post '/articles', {}, {'RAW_POST_DATA' => 'something'}

And in the controller:

#app/controllers/articles_controller.rb

def create
  puts request.body.read
end

Solution 8 - Ruby on-Rails

Using Rails 4, I was looking to do this to test the processing of raw xml that was being posted to the controller. I was able to do it by just providing the string to the post:

raw_xml = File.read("my_raw.xml")
post :message, raw_xml, format: :xml

I believe if the parameter provided is a string, it just gets passed along to the controller as the body.

Solution 9 - Ruby on-Rails

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.

Solution 10 - Ruby on-Rails

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]

Solution 11 - Ruby on-Rails

As of Rails 4.1.5, this was the only thing that worked for me:

class LegacyOrderUpdateControllerTest < ActionController::TestCase
  def setup
    @request.headers["Content-Type"] = 'application/json'
  end

  test "sending json" do
    post :index, '{"foo":"bar", "bool":true}'.to_json, { account_id: 5, order_id: 10 }
  end
end

for a url at /accounts/5/orders/10/items. This gets the url params conveyed as well as the JSON body. Of course, if orders is not embedded then you can leave off the params hash.

class LegacyOrderUpdateControllerTest < ActionController::TestCase
  def setup
    @request.headers["Content-Type"] = 'application/json'
  end

  test "sending json" do
    post :index, '{"foo":"bar", "bool":true}'.to_json
  end
end

Solution 12 - Ruby on-Rails

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

Solution 13 - Ruby on-Rails

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbrianView Question on Stackoverflow
Solution 1 - Ruby on-RailsbbrowningView Answer on Stackoverflow
Solution 2 - Ruby on-RailsAndrea ReginatoView Answer on Stackoverflow
Solution 3 - Ruby on-RailsArtem VasilievView Answer on Stackoverflow
Solution 4 - Ruby on-RailsGrimmoView Answer on Stackoverflow
Solution 5 - Ruby on-RailsRichView Answer on Stackoverflow
Solution 6 - Ruby on-RailsPebView Answer on Stackoverflow
Solution 7 - Ruby on-RailsDaniel Salmeron AmselemView Answer on Stackoverflow
Solution 8 - Ruby on-RailsTom RossiView Answer on Stackoverflow
Solution 9 - Ruby on-RailsJuan GomezView Answer on Stackoverflow
Solution 10 - Ruby on-RailsAlex ReisnerView Answer on Stackoverflow
Solution 11 - Ruby on-RailsAndrew GoodnoughView Answer on Stackoverflow
Solution 12 - Ruby on-Railsx-yuriView Answer on Stackoverflow
Solution 13 - Ruby on-RailsSohanView Answer on Stackoverflow