Rspec-rails: Rails 5 / Rspec 3.5.0 -- setting request headers?

Created on 5 Jul 2016  路  42Comments  路  Source: rspec/rspec-rails

Hi,

I had a spec working under Rails 4 / Rspec 3.4.4 that looked like this:

it 'does something with the referrer' do
  allow(request).to receive(:referrer).and_return('aol.com')
  expect(Foo).to receive(:bar).with('aol.com')
  get :endpoint
end

After upgrading, it looks like request.referrer is not stubbed correctly and request.referrer in the controller returns nil.

I tried rewriting like so:

get :endpoint, { params: {} }, headers: { 'HTTP_REFERER' => 'aol.com' }

But I wasn't able to get that header, or actually any header passed through to the controller.

Any suggestions?

Has reproduction case

Most helpful comment

I worked around this like so. Maybe it helps you, too. Not the prettiest solution but effective.

  let(:headers) { some: "header" }
  # ...
  request.headers.merge! headers
  post :endpoint, params: params

All 42 comments

Setting the header in request.env works -- is that the recommended way to accomplish this?

Try: get :endpoint, params: {}, headers: { 'HTTP_REFERER' => 'aol.com' }

@ledbettj
Thanks for the issue. We need a little more detail to be able to reproduce this.

Could you please provide us with a rails app that we can clone that demonstrates the issue. Specifically it'd be great if

1) you could rails new an application and commit
2) make all the changes necessary to reproduce the issue and commit

then, provide us with a description of how to clone your application and reproduce the issue.

Thanks :)

I've pushed a basic example here.

ApplicationController#index simply renders the HTTP referrer as JSON. The specs for this action try to set the referrer in several ways; only the last -- setting request.env -- works.

git clone https://github.com/ledbettj/rspec-repro.git repro
cd repro
bundle
bundle exec rake spec

Output:

Failures:

  1) ApplicationController when stubbing referrer sets the referrer correctly
     Failure/Error: expect(body['from']).to eq(aol)

       expected: "aol.com"
            got: nil

       (compared using ==)
     # ./spec/controllers/application_controller_spec.rb:11:in `block (3 levels) in <top (required)>'

  2) ApplicationController when passing referrer as a hash sets the referrer correctly
     Failure/Error: expect(body['from']).to eq(aol)

       expected: "aol.com"
            got: nil

       (compared using ==)
     # ./spec/controllers/application_controller_spec.rb:18:in `block (3 levels) in <top (required)>'

  3) ApplicationController when passing in two hashes sets the referrer correctly
     Failure/Error: expect(body['from']).to eq(aol)

       expected: "aol.com"
            got: nil

       (compared using ==)
     # ./spec/controllers/application_controller_spec.rb:25:in `block (3 levels) in <top (required)>'

Finished in 0.04602 seconds (files took 2.82 seconds to load)
4 examples, 3 failures

Let me know if anything else would help.

request.env did not work for me. Any update on this?

@richddr looks like nobody from the RSpec core team has had a chance to look at this yet. If you really need a fix to this, you're more than welcome to use the reproduction case above as a starting point for debugging and see if you can fix the problem :)

I worked around this like so. Maybe it helps you, too. Not the prettiest solution but effective.

  let(:headers) { some: "header" }
  # ...
  request.headers.merge! headers
  post :endpoint, params: params

I tried to fix this issue but don't see anything wrong with the way it currently is. (I don't know if this is a regression)

Just to explain as to why this is happening.

Regarding the stubbed method. If you take a look at ActionController::TestCase::Behaviour you will notice that all HTTP methods (get, post, delete, ...) get funneled into the process method. The process method builds a new TestRequest object with the request.env object from your test. That's why stubbed methods won't work.

Regarding the remaining two options - passing the headers as params. This also won't work. If you take a look at the above-mentioned class you can notice that only the session, flash, method, body, xhr params get processed and all others are ignored.

This leaves you with the option to edit request.env direcly or through request.headers as @sebastian-julius outlined.

Sorry, forgot to post an update here. I also ended up working around it the same as @sebastian-julius did.

You cannot stub request because it is recreated in this line of actionpack/lib/action_controller/test_case.rb:

  @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class

So, the request seen in the controller action is another instance of the request accessible/modified in the spec, and neither all the env part are kept.

I expected this to work:

get :create, headers: { Key: "SomeKey" }

Ended up doing this, per @sebastian-julius's workaround:

headers = { :Key => @api_key.token }
request.headers.merge! headers
get :create

For me the issue is that req.headers['CustomHeader'] is nil while req.env['CustomHeader'] contains required value. For production environment it's the other way around with Rails 5.0 and rspec 3.5.0.
Am i missing something?

I faced this issue today and I dug a little. It seems that for request specs, rspec-rails correctly leads us to ActionDispatch::IntegrationTest::Behavior as seen here: https://github.com/rspec/rspec-rails/blob/ace2f2b922a7e5f79bfa8f667a87b1ef5342425e/lib/rspec/rails/example/request_example_group.rb#L15

But for controller tests, it leads us to the old and now deprecated ActionController::TestCase::Behavior, as seen here: https://github.com/rspec/rspec-rails/blob/ace2f2b922a7e5f79bfa8f667a87b1ef5342425e/lib/rspec/rails/example/controller_example_group.rb#L13

ActionDispatch::IntegrationTest has support for extracting the header key and setting headers normally works, but breaks in ActionController::TestCase and hence it works in request specs, but not in controller specs.

I think this is a bug and the core team should take a look at it.

Nothing of the above mentioned fixes worked for me. Ended up with:

before do
  allow_any_instance_of(ActionController::TestRequest).to receive(:referer) do
    '/imprint'
  end
end

As for @JonRowe 's example, you cannot set the headers explicitly in the request as we can in the method's signature in 5.0 and 5.1. There is no headers option. There is a `headers' option in the integration test version though.

@jfragoulis This method delegates to Rails, it supports the same parameter format as your version of Rails.

Here's a bit of context regarding why this issue isn't getting much attention: request specs are preferred over controller specs

This recommendation like the one near the top will work in a request spec:

get '/endpoint', params: {}, headers: { 'HTTP_REFERER' => 'aol.com' }

This worked for me with Rails 5.1.4 and RSpec 3.6.

it 'authenticates the user correctly' do
    user = User.create! valid_attributes
    request.headers['Authorization'] = "Token #{user.token}"
    get :index
    expect(response).to have_http_status(:success)
end

Hope it helps.

Everyone seems to have found the way to do it. In request specs you can pass the headers as an argument, in controller specs you need to set the headers directly on the request object.

Should this be marked as closed then?

Does the request object get reset between requests?

It seems odd if you want to do two concurrent requests, one with a header and one without. In between them you have modify the request params.
my preferred:

get :show, id: 1, headers: { 'Authenticated token' }
response.code.should be 200

get :show, id:1
response.code.should be 401

instead of:

request.headers['Authorization'] = "Token #{user.token}"
get :show, id: 1
response.code.should be 200

request.headers['Authorization'] = "nil"
get :show, id:1
response.code.should be 401

The headers and the params should be treated the same way

@KevinColemanInc between requests no, and why should it? It resets for each test, and each test should have one request.

The only thing that worked for me. (rails: 5.1.4 & rspec-rails 3.7.2)

before(:all) do
    header 'Authorization', "Bearer <token>"
    get '/my/endpoint'
end

I ended up here trying to figure out how to mock the request.domain ... since doubles are not allowed in global RSpec context, I just brute forced it by overriding the method on TestRequest like this:

ActionController::TestRequest.class_eval do
  def domain
    "example.com"
  end
end

post "/widgets", :params => { :widget => {:name => "My Widget"} }, :headers => headers
Works fine for me with rails 5.1.4 and rspec-rails 3.7

@andreymakovenko yup, it works in request specs.

why so many downvotes on @andreymakovenko reply?

@ArielAleksandrus this issue is about controller specs not request specs.

for example:
request.headers.merge!(valid_headers)
post :create, params: { order: order.attributes }

Hello, so... nothing works for me :p I've tried everything, I think I'm doing something wrong. I'm using rails 5.2.0 and rspec-rails 3.8.0 and I tried doing:

1) header 'access-token', my_token
2) request.headers['access-token'] = my_token
3) request.env['HTTP_ACCESS-TOKEN'] = my_token
4) request.headers.merger!({'access-token': my_token})
5) get my_path, :headers => {'access-token' => token}
6) get my_path, nil, :headers => {'access-token' => token}
7) get my_path, params: {}, :headers => {'access-token' => token}

NOTHING WORKS, am I dumb?
1), 5), 6) and 7) simply don't set the headers, and the request returns a 401 Unauthorized instead of 200 OK
2), 3) and 4) don't work cause it says request needs more arguments (1..2 arguments), meaning it's a function...

Here's the spec file:

require 'spec_helper'

describe Transaction, :type => :api do
    context 'get all balances' do
        before do
            user = User.create(
                name: 'John Smith', 
                email: '[email protected]', 
                password: '123456', password_confirmation: '123456',
                mobile_number: '999199389')
            client, token = user.create_token
            # header 'access-token', token
            # header 'client', client
            # header 'uid', user.email
            # request.headers['Access-Token'] = token
            # request.headers['Client'] = client
            # request.headers['Uid'] = user.email
                # headers = { :access_token => token, :client => client, :uid => user.email }
                # request.headers.merge! headers
            get '/allbalances', :headers => {'access-token' => token, 'client' => client, 'uid' => user.email}
        end

        it 'responds with OK status' do
            byebug
            expect(last_response.status).to eq 200
        end
    end
end

Someone help me pleassse

Dependant on what your type: :api triggers here, will depend on how you send headers

Sorry, my bad, I'm using controller type now, but the main issue was something else, unrelated do Rspec. Everything works now! Thanks!

Rails 5.2 this works for me
post path, params: params.to_json, headers: headers

Before you post your solution please make sure that you understand the difference between request specs and controller specs. This issue is about controller specs.
Reading all the replies that aren't helping is really frustrating.

@fnmendez works well (in controller specs), I recommend to check in controller if simple "puts" is the values are right placed; I spend 5 hours thinking that the solution not works, but, I was not the right values in models (fixtures)

get '/foobar.json', {}, { 'HTTP_SESSION_ID' => 'XXXX', .... }

Using this works for controller/feature specs. The only thing you have to make sure is that the headers are on the second position of the args. That's why the empty hash.
Since they get utilized here https://github.com/rails/rails/blob/e2fcb2b4aec69e10a01cebfe51bbd280ce6d5a93/actionpack/lib/action_controller/test_case.rb#L602

As @pawandubey its the different module request and controller specs are using.

Unfortunatly when you use the snippet like top one rubocop will complain because
of Rails/HttpPositionalArguments: Use keyword arguments instead of positional arguments for http call
This also a topic over at rubocop-hq/rubocop#5888. But it seems stall.

For HTTP_REFERER use symbol and not string !

post "/api/v1/kpulse", params: params, headers: { HTTP_REFERER: 'kpulse.fr' }

This solution worked for me too
https://robert-reiz.com/2013/08/09/http_referer-for-rspec-is-missing/

post "/sessions", {:session => {:email => user.email, :password => user.password}}, {"HTTPS" => "on", 'HTTP_REFERER' => '/signin'}

For Rails 5.2.3 and Rspec 3.8
Just used the request.headers.merge!(valid_headers)

let(:valid_attributes) {
  {to_wallet: "wallet", value: 100.00}
}

let(:valid_headers) { { HTTP_USERTOKEN: "Token12345678"} }

it "creates a new Transaction" do
  request.headers.merge!(valid_headers) # This line right here!!!
  expect {
    post :create, params: {transaction: valid_attributes}
  }.to change(Transaction, :count).by(1)
end

Hello from 2019!

Just want to confirm I came here trying to set headers on a controller test and that @sebastian-julius ' approach (though I slightly modified it) worked great for me! Thank you! :)

This works for me on rspec 3.8.0

it 'return 200' do
  auth = 'Basic ' + Base64.strict_encode64('username:password')
  params = { foo: 'bar' }

  header('Authorization', auth)

  get '/baz', params

  expect(last_response.status).to eq 200
end

For me the issue is that req.headers['CustomHeader'] is nil while req.env['CustomHeader'] contains required value. For production environment it's the other way around with Rails 5.0 and rspec 3.5.0.
Am i missing something?

Same! It was request.headers['myHeader'] was missing. Man that took me hours.

Are there any volunteers to update the doc and add feature test similar to the cookie one?

Was this page helpful?
0 / 5 - 0 ratings