Setting token and header in rspec test for JSON API - ruby-on-rails

I'm trying to test a json written in rails in rspec. I'm not sure about the syntax. This is what I have so far:
require 'rails_helper'
RSpec.describe V1::VersionsController, :type => :controller do
before do
#token = "0"
request.env["Content-Type"] = 'application/vnd.api+json; charset=utf-8'
end
describe "GET index" do
it "renders the index JSON" do
#request.headers['Content-Type'] = 'application/vnd.api+json; charset=utf-8'
request.env['Content-Type'] = 'application/vnd.api+json; charset=utf-8'
params = { token: #token }
get :index, :format => :json, token: #token
#response.should be_success
body = JSON.parse(response.body)
ap body
end
end
end
I tried it in a bunch of different ways as you can see. But I'm getting a 403 error.
I'm using Rails 5.0.0.beta3, ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux] and rspec-3.1.

So it turns out, the problem was not with my code, but the fact that my test database was not populated with the token. I had a rake task for this and just running it with RAILS_ENV=test solved the issue. Also the code in my question is not very clean as I'm trying a bunch of different stuff to achieve the same result. Here's the final spec for anyone who might be interested:
require 'rails_helper'
RSpec.describe V1::VersionsController, :type => :controller do
before do
#token = "0"
#request.headers['Content-Type'] = 'application/vnd.api+json; charset=utf-8'
end
describe "GET index" do
it "renders the index JSON" do
get :index, :format => :json, token: #token
body = JSON.parse(response.body)
expect(response.status).to eq 200
expect(body["meta"]["app_name"]).to eq Rails.application.class.parent_name
expect(body["meta"]["app_version"]).to eq ApplicationName.version
end
end
end

Related

Passing bearer token in RSPEC (no implicit conversion of nil into String)

require 'rails_helper'
RSpec.describe "User management", :type => :request do
describe "Example::V2::Users" do
describe "GET /api/v2/users/" do
it 'returns status 200, authorized' do
#token = "Bearer 123"
#url = "https://api.example-v2.com/v2/users/me"
#headers = { "AUTHORIZATION" => #token}
get #url, as: :json, headers: {:Authorization => #token}
expect(response.status).to eq 200
end
end
end
end
I am trying to pass the #token but I am getting this error
Failure/Error: get #url, as: :json, headers: {:Authorization => #token}
TypeError: no implicit conversion of nil into String
I can make a get request without the params and headers and it works but as soon as I add params or headers I get the error, I even tried writing it like so
1 - get #url, {}, { Authorization: #token}
ArgumentError: wrong number of arguments (given 2, expected 1)
2 - get #url, params: {}, headers: { Authorization: #token}
TypeError: no implicit conversion of nil into String
Some smart people please point me in the right direction =).
Gems:
gem 'rspec-rails', '~> 3.8'
gem 'rails', '~> 6.0.2.2'
get #url, as: :json, headers: { Authorization: #token }
this works in my environment when type: reuqst.
I suggest you can use byebug just before the GET request
and check the #token and the #url.
If everything looks good.
Check the get if it can make request to root url?
Check the rails_helper if it requires any suspicious file?
Please check this its working for me.
require 'rails_helper'
RSpec.describe "User management", :type => :request do
describe "PeopleGoal::V2::Users" do
describe "GET /api/v2/users/" do
it 'returns status 200, authorized' do
request.headers["AUTHORIZATION"] = "Basic #{user.id}"
#url = "https://api.peoplegoal-v2.com/v2/users/me"
get #url, format: :json
expect(response.status).to eq 200
end
end
end
end

RSpec request spec test always returning empty response

I'm currently putting together a project on Rails (4.1.8) and am trying out writing RSpec request specs to test the api routes. Strangely however it also seems to return a empty response when running the test, however doing a direct request with Postman returns the json data as expected.
Request Spec:
# spec/requests/tasks_spec.rb
require 'rails_helper'
RSpec.describe 'Tasks API', type: :request do
render_views
describe 'GET /tasks' do
it 'returns tasks' do
headers = {
"ACCEPT" => "application/json"
}
get '/tasks', headers
puts "Response: #{response}"
expect(response.content_type).to eq("application/json")
expect(json).not_to be_empty
end
end
end
Result:
Failure/Error: expect(json).not_to be_empty
expected `[].empty?` to return false, got true
The Tasks Controller is setup as follows:
class TasksController < ApplicationController
before_action :set_task, only: [:show, :update, :destroy]
# GET /tasks
def index
#tasks = Task.all
json_response(#tasks)
end
Response Concern:
module Response
def json_response(object, status = :ok)
render json: object, status: status
end
end
Spec Support:
module RequestSpecHelper
# Parse JSON response to ruby hash
def json
JSON.parse(response.body)
end
end
I had read similar questions which talk about a rende_views fix - however I've added that with no effect and also I thought you didn't need these when using Request specs as they should run through the full stack? Can anyone shed some light, quite new to request specs and a bit puzzled.
There is no tasks in your DB, that's why it is returning an empty array.
You need to create a task before calling index action.
Either you can create a factory using the facatory_girl_rails gem for Task or you can call the create action of TasksController before calling index action:
# spec/requests/tasks_spec.rb
require 'rails_helper'
RSpec.describe 'Tasks API', type: :request do
render_views
describe 'GET /tasks' do
before do
FactoryGirl.create(:task)
end
it 'returns tasks' do
headers = {
"ACCEPT" => "application/json"
}
get '/tasks', headers
puts "Response: #{response}"
expect(response.content_type).to eq("application/json")
expect(json).not_to be_empty
end
end
end
Reference FactoryGirl(Defining factories):
https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#defining-factories

Why is RSpec not passing my headers to Rails?

I am trying to test my Rails application using RSpec, but my tests are failing because RSpec seems to not be passing the headers I give it to Rails.
I have a UsersController that includes ApplicationHelper, and in ApplicationHelper I have a method that accesses the headers hash. Indexing it by my SESSION_KEY header returns nil. If I puts headers inside that method, the hash does not contain the header I have supplied, only the following: {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff"}.
Here is the relevant part of my RSpec spec:
require 'rails_helper'
describe Api::V1::UsersController, type: :request do
let(:user) { User.create(name: 'TestUser', email: 'someone#example.com', password: 'password123', password_confirmation: 'password123') }
let(:id) { user.id }
let(:sess) { user.sessions.create }
before { get "/api/v1/users/#{id}" }
# Snipped other tests
context 'with authentication' do
context 'with a valid id' do
it 'returns full user information' do
get "/api/v1/users/#{id}", nil, {'HTTP_SESSION_KEY': sess.key}
response_user = response_json[:user]
expect(response.status).to eq 200
expect(response_user).to_not be_nil
expect(response_user[:name]).to eq user[:name]
expect(response_user[:email]).to eq user[:email]
end
end
end
def response_json
JSON.parse(response.body, symbolize_names: true)
end
end
I have also tried passing the SESSION_KEY header without HTTP_ before it, and that did not work. I have also tried moving it up to the top get in the before block to see if it was a context issue, and that did not work either.
Docs say the above should work, but if for some reason rspec is interpreting your test as a :controller test and not a :request test then you need to do this (just before your get call):
request.env["HTTP_SESSION_KEY"] = sess.key

Setting Accept: to application/json for controller specs

Writing specs for a json api the routes are defaulted to only accept json requests like so:
Rails.application.routes.draw do
namespace :api, default: { format: 'json' } do
namespace :v1 do
resources :users, only: [:create]
end
end
end
I keep getting the following error:
Failure/Error: post :create, json
ActionController::UrlGenerationError:
No route matches {:action=>"create", :company_name=>"Wilderman, Casper and Medhurst", :controller=>"api/v1/users", :email=>"lillie_prohaska#example.com", :password=>"difdcbum5q", :username=>"gielle"}
Traditionally to get around this error I have set the CONTENT_TYPE and the HTTP_ACCEPT on the request so that it will pass the json formatting requirement.
My specs are written like so:
describe Api::V1::UsersController do
before :each do
#request.env['HTTP_ACCEPT'] = 'application/json'
#request.env['CONTENT_TYPE'] = 'application/json'
end
describe "POST#create" do
context "with valid attirbutes" do
let(:json) { attributes_for(:user) }
it "creates a new user" do
expect{ post :create, json }.to change{ User.count }.by(1)
end
it "returns status code 200" do
post :create, json
expect(response.status).to be(200)
end
it "should contain an appropriate json response" do
post :create, json
user = User.last
json_response = {
"success" => true,
"id" => user.id.to_s,
"auth_token" => user.auth_token
}
expect(JSON.parse(response.body)).to eq (json_response)
end
end
end
end
I have also tried adding a hash with { format: 'json' } which has also failed me.
According to the comments on the accepted answer for the question below setting the request environment headers will no longer work with rspec 3:
Set Rspec default GET request format to JSON
How would this be achieved in rspec 3 in Rails 4.1.1?
Thanks!
Here is how I do it over here:
[:xml, :json].each do |format|
describe "with #{format} requests" do
let(:api_request) { { :format => format } }
describe "GET 'index'" do
before :each do
api_request.merge attributes_for(:user)
end
it 'returns HTTP success' do
get :index, api_request
expect(response).to be_success
end
end
end

Set Rspec default GET request format to JSON

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

Resources