Set Rspec default GET request format to JSON

Ruby on-RailsRspecFunctional Testing

Ruby on-Rails Problem Overview


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)

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

before :each do
  request.env["HTTP_ACCEPT"] = 'application/json'
end

Solution 2 - Ruby on-Rails

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

Solution 3 - Ruby on-Rails

The following works for me with rspec 3:

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

This sets HTTP_ACCEPT.

Solution 4 - Ruby on-Rails

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.

Solution 5 - Ruby on-Rails

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

Solution 6 - Ruby on-Rails

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.

Solution 7 - Ruby on-Rails

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

Solution 8 - Ruby on-Rails

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

Solution 9 - Ruby on-Rails

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)

Solution 10 - Ruby on-Rails

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: https://stackoverflow.com/questions/9576756/using-rspec-how-do-i-test-the-json-format-of-my-controller-in-rails-3-0-11

Solution 11 - Ruby on-Rails

https://stackoverflow.com/questions/2543885/why-dont-rspecs-methods-get-post-put-delete-work-in-a-controller-s

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

[1]: https://github.com/rails/rails/blob/32395899d7c97f69b508b7d7f9b7711f28586679/actionpack/lib/action_controller/test_case.rb "ActionController::TestCase"

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
QuestionDrazenView Question on Stackoverflow
Solution 1 - Ruby on-RailsPratik KhadloyaView Answer on Stackoverflow
Solution 2 - Ruby on-RailsknoopxView Answer on Stackoverflow
Solution 3 - Ruby on-RailsschmijosView Answer on Stackoverflow
Solution 4 - Ruby on-RailsFranklin YuView Answer on Stackoverflow
Solution 5 - Ruby on-RailsDan KohnView Answer on Stackoverflow
Solution 6 - Ruby on-Railszw963View Answer on Stackoverflow
Solution 7 - Ruby on-RailssetthelineView Answer on Stackoverflow
Solution 8 - Ruby on-RailsDBrownView Answer on Stackoverflow
Solution 9 - Ruby on-RailsmpospelovView Answer on Stackoverflow
Solution 10 - Ruby on-RailsNico BrennerView Answer on Stackoverflow
Solution 11 - Ruby on-RailsKenneth ChengView Answer on Stackoverflow