I have a controller in rails such that when i go to the url :
/my-path
it outputs json text.
There is no authorization involved.
When I open the URL in browser or postman i get the expected json data.
I can't test it in rspec as i get a no authorization error.
before do
get base_url , params: {} #, headers: { 'Content-Type' => 'application/x-www-form-urlencoded', Authorization: 'Token: sdaf' }
end
it 'returns 200' do
puts response
expect(response.code).to eq '200'
end
When i do the above, i get the following error :
Failure/Error: expect(response.code).to eq '200'
expected: "200"
got: "401"
Authorization header missing.
I am new to rails, so i have little idea on what i am missing out here.
Hope you are writing your rspec test in the controller test file, in that case if your writing test for say example home_controller, index action explictly specify controller action like below, hope it helps :)
before do
get :index, params: {} #, headers: { 'Content-Type' => 'application/x-www-
form-urlencoded', Authorization: 'Token: sdaf' }
end
rails 5.0.0.1
rspec 3.5
I have inherited a code base. I am busy writing integration tests to tie down the app functionality before I consider refactoring.
I have the following lines in a controller concern before_action. It seems to read the request body. The json value here is used to extract an identifier used to authenticate the request.
request.body.rewind
body = request.body.read
json = JSON.parse(body) unless body.empty?
I need to test that the authentication happens correctly.
How can I set the request.body for a GET request spec?
I think you should be able to do this via the request env RAW_POST_DATA
get root_path, {}, 'RAW_POST_DATA' => 'raw json string'
request.raw_post # "raw json string"
See:
How to send raw post data in a Rails functional test?
https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec
#rails_post_5
require "rails_helper"
RSpec.describe "Widget management", :type => :request do
it "creates a Widget and redirects to the Widget's page" do
headers = { "CONTENT_TYPE" => "application/json" }
post "/widgets", :params => '{ "widget": { "name":"My Widget" } }', :headers => headers
expect(response).to redirect_to(assigns(:widget))
end
end
or just
post "/widgets", params: '{ "widget": { "name":"My Widget" } }'
The setup is the following. For each http request the manager sends his credentials in the header(name,pw). These get checked against the entries in the db and if they succeed return the desired user object. How is it possible to implement basic http_auth in the request tests? I would like to add only the password and username and test the return value? Which is the goal of request tests,right? I tried the following without much success:
I created an AuthHelper module in spec/support/auth_helper.rb with
module AuthHelper
def http_login
user = 'test'
pw = 'test'
request.ENV['HTTP_AUTHORIZATION'] =ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
end
end
and use it in the requests/api/user_spec.rb as follows
include AuthHelper
describe "User API get 1 user object" do
before(:each) do
http_login
end
but i receive this error message. How can i fix this and enable my tests to pass http_auth? I read lot of similar topis and questions also here but
they apply mostly to older versions of rspec and rails and are not applying to my case
Thanks in advance!
Failure/Error: request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
NoMethodError:
undefined method `env' for nil:NilClass# ./spec/support
# ./spec/support/auth_helper.rb:5:in `http_login'
# ./spec/requests/api/user_spec.rb:8:in `block (2 levels) in <top (required)>'
Update
I moved the header generation inside a request. I looked up the Auth verb, so i think the assignment should work. I tested the ActionController call in rails console and received a string. I suppose this is also correct.My whole test now looks like this:
describe "User API get 1 user object", type: :request do
it 'Get sends back one User object ' do
headers = {
"AUTHORIZATION" =>ActionController::HttpAuthentication::Basic.encode_credentials("test","test")
# "AUTHORIZATION" =>ActionController::HttpAuthentication::Token.encode_credentials("test","test")
}
FactoryGirl.create(:user)
get "/api/1/user", headers
#json = JSON.parse(response.body)
expect(response).to be_success
# expect(response.content_type).to eq("application/json")
end
end
receiving the following error:
which incudles the line #buf=["HTTP Basic: Access denied.\n"] so access is still denied.
Failure/Error: expect(response).to be_success
expected `#<ActionDispatch::TestResponse:0x000000070d1d38 #mon_owner=nil, #mon_count=0, #mon_mutex=#<Thread::Mutex:0x000000070d1c98>, #stream=#<ActionDispatch::Response::Buffer:0x000000070d1c48 #response=#<ActionDispatch::TestResponse:0x000000070d1d38 ...>,
#buf=["HTTP Basic: Access denied.\n"], #closed=false>, #header={"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff", "WWW-Authenticate"=>"Basic realm=\"Application\"", "Content-Type"=>"text/html; charset=utf-8", "Cache-Control"=>"no-cache", "X-Request-Id"=>"9c27d4e9-84c0-4ef3-82ed-cccfb19876a0", "X-Runtime"=>"0.134230", "Content-Length"=>"27"}, #status=401, #sending_file=false, #blank=false,
#cv=#<MonitorMixin::ConditionVariable:0x000000070d1bf8 #monitor=#<ActionDispatch::TestResponse:0x000000070d1d38 ...>, #cond=#<Thread::ConditionVariable:0x000000070d1bd0>>, #committed=false, #sending=false, #sent=false, #content_type=#<Mime::Type:0x00000002af78f8 #synonyms=["application/xhtml+xml"], #symbol=:html, #string="text/html">, #charset="utf-8", #cache_control={:no_cache=>true}, #etag=nil>.success?`
to return true, got false
SOLUTION
This test is not polished (yet) but at least it passes now.
describe "User API get 1 user object", type: :request do
it 'Get sends back one User object ' do
#env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
FactoryGirl.create(:user)
get "/api/1/user", {}, #env
JSON.parse(response.body)
expect(response).to be_success
expect(response.status).to eq 200
end
end
Read the error carefully: undefined method `env' for nil:NilClass means request is nil. Are you trying to set the header before a test while you are defining the request later on in the test?
You might want to look at the documentation for an example on how to set headers.
If you're still stuck, post one of your tests as well.
This line looks suspicious:
request.ENV['HTTP_AUTHORIZATION'] =ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
Are you sure that "ENV" should be capitalized? I think it should be written like "env".
I'm trying to write tests for an API which requires an hmac signature on each request.
describe Api::V2::HmacController, :type => :controller do
render_views
it 'GET' do
get :index, timestamp: Time.now.to_i, format: :json
expect(response.status).to eq(200)
end
end
I would like to add
request.env['x-api-key'] = API_KEY
request.env['x-api-hmac'] = "Encode"(API_SECRET, "parameters of the request")
to each request.
I'm open to any type of solution.
Question: How can I wedge a hook after the request has been formed, but hasn't sent?
I'm thinking of overwriting rspec get / post method, but I'm not sure how.
You can do it from in the block
request.headers['x-api-key'] = API_KEY
You shouldn't set access the request headers through the env.
See here
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)
before :each do
request.env["HTTP_ACCEPT"] = 'application/json'
end
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
The following works for me with rspec 3:
before :each do
request.headers["accept"] = 'application/json'
end
This sets HTTP_ACCEPT.
Here is a solution that
works for request specs,
works with Rails 5, and
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:
Directly in spec/spec_helper.rb. This is not suggested; the file will be loaded even when testing library methods in lib/.
Directly in spec/rails_helper.rb.
(my favorite) In spec/support/default_format.rb, and be loaded explicitly in spec/rails_helper.rb with
require 'support/default_format'
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.
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
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.
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
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
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)
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?
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