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?
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
@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']
isnil
whilereq.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?
Most helpful comment
I worked around this like so. Maybe it helps you, too. Not the prettiest solution but effective.