Sending JSON Post Data in an Integration Test

Last night I ran into a vexing problem. I had an API endpoint in a Rails app I developed for a customer that accepts request parameters in both the classic application/x-www-form-urlencoded content type as well as application/json. JSON is a more compact format and is easier to scan when reading client logs, so it is now my preferred format for request POST data.

One of the side effects of this is that boolean values are not represented the same in both formats. URL encoding often sends "1" (a string) while JSON can encode a literal boolean true. Depending on how you write your controller code, you may need to handle both possibilities. Testing for this is surprisingly difficult, though.

Writing an integration test that submits JSON POST data is not straightforward. Since an integration test exercises the full Rails stack, if you don’t set up the headers properly, Rack won’t parse the data properly and the test will fail. Here is how you do it:

headers = { 'CONTENT_TYPE' => 'application/json' }
json = '{"foo":"bar","boolean":true}'
post "/api/endpoint.json", json, headers

Your API endpoint in this example will get a params hash with the keys foo and boolean with values bar (a String) and true, respectively.

If you use RSpec, this applies to request specs (RSpec 2.12+), too, since request specs are built on top of Rails integration tests.

Hat tip to this StackOverflow question, which I only found after 45 minutes of searching. Hopefully this post helps someone else find the solution sooner. The same technique can be used if you need another format in your POST data, such as XML. Just change the CONTENT_TYPE header.

(Also note that the trick to making this work changed sometime in the Rails 3.x timeframe. Solutions that worked in 2.x don’t in recent releases. I haven’t tested edge Rails yet to see if changes are required for Rails 4.)