54

I am doing functional tests for my controllers with Rspec. I have set my default response format in my router to JSON, so every request without a suffix will return JSON.

Now in rspec, i get an error (406) when i try

get :index 

I need to do

get :index, :format => :json 

Now because i am primarily supporting JSON with my API, it is very redundant having to specify the JSON format for every request.

Can i somehow set it to default for all my GET requests? (or all requests)

11 Answers 11

58
before :each do request.env["HTTP_ACCEPT"] = 'application/json' end 
Sign up to request clarification or add additional context in comments.

5 Comments

@Erik do you have an alternate suggestion, since it doesn't work in Rspec 3?
The new Rspec 3 syntax would be much apreciated
Where do you see the "request" variable?
Things might have changed in RSpec3 world.
This works for me in Rspec 3+, request.accept = "application/json"
25

Put this in spec/support:

require 'active_support/concern' module DefaultParams extend ActiveSupport::Concern def process_with_default_params(action, parameters, session, flash, method) process_without_default_params(action, default_params.merge(parameters || {}), session, flash, method) end included do let(:default_params) { {} } alias_method_chain :process, :default_params end end RSpec.configure do |config| config.include(DefaultParams, :type => :controller) end 

And then simply override default_params:

describe FooController do let(:default_params) { {format: :json} } ... end 

5 Comments

This is so nice! Thanks for sharing. And just a hint. Because of #merge used in #process_with_default_params you can still "override" format in your spec to something else than json - to html for example.
Maybe everything after the action should be defaulted to not be mandatory?
This didn't actually work for me but this similar solution did: snip2code.com/Snippet/13535/…
I'm getting an undefined method alias_method_chain -- is there a fix for that?
@RonLugge You can look at my answer below.
22

The following works for me with rspec 3:

before :each do request.headers["accept"] = 'application/json' end 

This sets HTTP_ACCEPT.

1 Comment

this works for me with Rails 5.0.1 + RSpec 3.5.2 + JBuilder 2.6.0.
13

Here is a solution that

  1. works for request specs,
  2. works with Rails 5, and
  3. does not involve private API of Rails (like process).

Here's the RSpec configuration:

module DefaultFormat extend ActiveSupport::Concern included do let(:default_format) { 'application/json' } prepend RequestHelpersCustomized end module RequestHelpersCustomized l = lambda do |path, **kwarg| kwarg[:headers] = {accept: default_format}.merge(kwarg[:headers] || {}) super(path, **kwarg) end %w(get post patch put delete).each do |method| define_method(method, l) end end end RSpec.configure do |config| config.include DefaultFormat, type: :request end 

Verified with

describe 'the response format', type: :request do it 'can be overridden in request' do get some_path, headers: {accept: 'text/plain'} expect(response.content_type).to eq('text/plain') end context 'with default format set as HTML' do let(:default_format) { 'text/html' } it 'is HTML in the context' do get some_path expect(response.content_type).to eq('text/html') end end end 

FWIW, The RSpec configuration can be placed:

  1. Directly in spec/spec_helper.rb. This is not suggested; the file will be loaded even when testing library methods in lib/.

  2. Directly in spec/rails_helper.rb.

  3. (my favorite) In spec/support/default_format.rb, and be loaded explicitly in spec/rails_helper.rb with

    require 'support/default_format' 
  4. In spec/support, and be loaded by

    Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 

    which loads all the files in spec/support.

This solution is inspired by knoopx's answer. His solution doesn't work for request specs, and alias_method_chain has been deprecated in favor of Module#prepend.

Comments

7

In RSpec 3, you need make JSON tests be request specs in order to have the views render. Here is what I use:

# spec/requests/companies_spec.rb require 'rails_helper' RSpec.describe "Companies", :type => :request do let(:valid_session) { {} } describe "JSON" do it "serves multiple companies as JSON" do FactoryGirl.create_list(:company, 3) get 'companies', { :format => :json }, valid_session expect(response.status).to be(200) expect(JSON.parse(response.body).length).to eq(3) end it "serves JSON with correct name field" do company = FactoryGirl.create(:company, name: "Jane Doe") get 'companies/' + company.to_param, { :format => :json }, valid_session expect(response.status).to be(200) expect(JSON.parse(response.body)['name']).to eq("Jane Doe") end end end 

As for setting the format on all tests, I like the approach from this other answer: https://stackoverflow.com/a/14623960/1935918

1 Comment

I thought config.render_views was sufficient for getting JSON view responses back.
6

Perhaps you could add the first answer into spec/spec_helper or spec/rails_helper with this:

config.before(:each) do request.env["HTTP_ACCEPT"] = 'application/json' if defined? request end 

if in model test (or any not exist request methods context), this code just ignore. it worked with rspec 3.1.7 and rails 4.1.0 it should be worked with all rails 4 version generally speaking.

1 Comment

If you change this to config.before(:each, type: :controller), then it won't hit model specs.
2

Running Rails 5 and Rspec 3.5 I had to set the headers to accomplish this.

post '/users', {'body' => 'params'}, {'ACCEPT' => 'application/json'} 

Thi matches what the example in the docs looks like:

require "rails_helper" RSpec.describe "Widget management", :type => :request do it "creates a Widget" do headers = { "ACCEPT" => "application/json", # This is what Rails 4 accepts "HTTP_ACCEPT" => "application/json" # This is what Rails 3 accepts } post "/widgets", { :widget => {:name => "My Widget"} }, headers expect(response.content_type).to eq("application/json") expect(response).to have_http_status(:created) end end 

Comments

2

Per the Rspec docs, the supported method is through the headers:

require "rails_helper" RSpec.describe "Widget management", :type => :request do it "creates a Widget" do headers = { "ACCEPT" => "application/json", # This is what Rails 4 and 5 accepts "HTTP_ACCEPT" => "application/json", # This is what Rails 3 accepts } post "/widgets", :params => { :widget => {:name => "My Widget"} }, :headers => headers expect(response.content_type).to eq("application/json") expect(response).to have_http_status(:created) end end 

Comments

1

For those folks who work with request tests the easiest way I found is to override #process method in ActionDispatch::Integration::Session and set default as parameter to :json like this:

module DefaultAsForProcess def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: :json) super end end ActionDispatch::Integration::Session.prepend(DefaultAsForProcess) 

2 Comments

I feel like monkeypatching the class is the wrong way to go about this when there's other viable solutions.
@AndrewKS probably it’s not the best solution but I found it most applicable to my cases as others have some issues with different types of requests, for example get params should be query string, post params should be in the body etc. setting header doesn’t take this cases into account but setting ‘as’ in request just makes the request in most expected way and it relies on builtin behavior
0

Not sure if this will work for this specific case. But what I needed in particular was to be able to pass a params hash to the post method. Most solutions seem to be for rspec 3 and up, and mention adding a 3rd parameter like so:

post '/post_path', params: params_hash, :format => 'json' 

(or similar, the :format => 'json' bit varies)

But none of those worked. The controller would receive a hash like: {params: => { ... }}, with the unwanted params: key.

What did work (with rails 3 and rspec 2) was:

post '/post_path', params_hash.merge({:format => 'json'}) 

Also check this related post, where I got the solution from: Using Rspec, how do I test the JSON format of my controller in Rails 3.0.11?

Comments

-3

Why don't RSpec's methods, "get", "post", "put", "delete" work in a controller spec in a gem (or outside Rails)?

Based off this question, you could try redefining process() in ActionController::TestCase from https://github.com/rails/rails/blob/32395899d7c97f69b508b7d7f9b7711f28586679/actionpack/lib/action_controller/test_case.rb.

Here is my workaround though.

describe FooController do let(:defaults) { {format: :json} } context 'GET index' do let(:params) { defaults } before :each do get :index, params end # ... end context 'POST create' do let(:params) { defaults.merge({ name: 'bar' }) } before :each do post :create, params end # ... end end 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.