14

I'm using the rails-api gem to build a web service and want to test my API with RSpec. Every request I make, regardless of the HTTP method has the CONTENT_TYPE header set as "application/x-www-form-urlencoded". This isn't really a problem until I try to use wrap_parameters in my controller and it's not have any affect on the params hash:

class ApplicationController < ActionController::API
  include ActionController::ParamsWrapper
end

class ProjectsController < ApplicationController
  wrap_parameters :project, include: [:name]
  # ...
end

This hack no longer works (@request is nil), and none of the other Stack Overflow posts I found work either.

If I make the following request in my RSpec test:

put "/projects/1.json", {name: 'Updated Project 1'}

and put a debugger in my controller I get:

(rdb:1) p params
    { "name"=>"Updated Project 1",
  "action"=>"update",
  "controller"=>"projects",
  "id"=>"5539bbd9-010c-4cfb-88d3-82dadbc99507",
  "format"=>"json"
}

(rdb:1) p request.content_type
"application/x-www-form-urlencoded"

I'm expecting to see something like this for the params hash (note the addition of the project key):

{ "name"=>"Updated Project 1",
  "action"=>"update",
  "controller"=>"projects",
  "id"=>"5539bbd9-010c-4cfb-88d3-82dadbc99507",
  "format"=>"json",
  "project" => {"name" => "Updated Project 1"}
}

Is it possible to set the content type header using just RSpec? Or do I have have to use rack/test for this functionality?

Community
  • 1
  • 1
Peter Brown
  • 50,956
  • 18
  • 113
  • 146
  • There's a chance that [wrap_parameters is still busted](https://github.com/rails-api/rails-api/issues/33) with the rails-api project and this has nothing to do with setting the content type. – Peter Brown Dec 18 '12 at 20:31
  • Confirmed that wrap_parameters works by adding the :url_encoded_form format to method call: `wrap_parameters format: [:url_encoded_form, :json]` – Peter Brown Dec 18 '12 at 20:52
  • You can set elements of the rspec request environment in a controller with `request.env['HTTP_CONTENT_TYPE'] = 'application/json'` or whatever else you need. – nmott Dec 19 '12 at 09:59

7 Answers7

22

A lot of frustration and variations and that's what worked for me. Rails 3.2.12 Rspec 2.10

 @request.env["HTTP_ACCEPT"] = "application/json"
 @request.env["CONTENT_TYPE"] = "application/json"
 put :update, :id => 1, "email" => "bing@test.com"

wrap_parameters seems to be working declared this way

wrap_parameters User, format: :json

being used for User model

VelLes
  • 1,032
  • 1
  • 10
  • 19
  • 1
    This really tripped me up. Setting `put :update, :id => 1, :format => :json` will **not** work. You must enter as VelLes has. – maletor Nov 01 '13 at 00:10
7

This worked for me Rails 4.0.3 and Rspec 2.14.1 if anyone is looking for more recent versions.

put '/projects/1.json', {name: 'Updated Project 1'}, {
  'HTTP_ACCEPT' => 'application/json',
  'CONTENT_TYPE' => 'application/json'
}

and

wrap_parameters Project, format: :json
kmanzana
  • 1,138
  • 1
  • 13
  • 23
  • 1
    I removed .to_json of params and it worked: `put '/projects/1.json', {name: 'Updated Project 1'}, { 'HTTP_ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }` – Kesha Antonov Nov 04 '15 at 08:54
5

Using the new Rails v5.0.x API only settings I found that this problem with rails defaulting everything to "application/x-www-form-urlencoded" is still in issue for testing with RSpec-Rails Requests

Here is what I did to fix the problem:

Create support file at ./spec/support/json_requests.rb

Edit it to be something like this to override the behavior for all of your API only JSON requests:

module JsonRequests
  def get(*args)
    super(*json_args(*args))
  end

  def post(*args)
    super(*json_args(*args))
  end

  def update(*args)
    super(*json_args(*args))
  end

  def patch(*args)
    super(*json_args(*args))
  end

  def put(*args)
    super(*json_args(*args))
  end

  def delete(*args)
    super(*json_args(*args))
  end

  def json_args(path, params = {}, headers = {})
    [path, params.to_json, headers.merge('CONTENT_TYPE' => 'application/json')]
  end
end

RSpec.configure do |config|
  config.include JsonRequests, type: :request
end

Keep in mind that this will override all Specs within ./spec/requests so if you need to use "application/x-www-form-urlencoded" you could also include this module manually as needed in your Describe 'something' do block.

JoshuaBurke
  • 151
  • 2
  • 2
  • Using ruby `2.5.0`, rails `5.1.5` and rspec `3.7.1`, I had to change the above `json_args` method to this: `def json_args(path, params = {})` `[path, **params.merge(as: :json)]` `end` See here: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/integration.rb – John Donner Mar 17 '18 at 15:55
4

Rails 5 no hacks:

put(:update,
    params: {project_id: 1},
    body: {name: 'Updated Project 1'}.to_json,
    as: :json)

This sets the content_type correctly. In the controller params will hold both params and body.

thisismydesign
  • 21,553
  • 9
  • 123
  • 126
3

Its 2021, Rails 6.1 and I had to use as: :json to fix this wierd mangling of an array of hashes in the params.

put(:update, params: the_params_hash, as: :json)
Weston Ganger
  • 6,324
  • 4
  • 41
  • 39
0

If you are using Rails 4 (and rspec ~3.7) and don't want to use the inline syntax:

request.headers["CONTENT_TYPE"] = "application/json"
ryan0
  • 1,482
  • 16
  • 21
0

Rails 5

headers = { 'CONTENT_TYPE' => 'application/json' }
params = { user_type: 'tester' } 

and after that request like

post '/api/v1/users/test', params.to_json, headers

and also remove .to_json from request route

mmike
  • 706
  • 1
  • 8
  • 22