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