I am upgrading a legacy project to rails 5 and among the rspec tests that are failing I have one that says:
Failure/Error: expect(response).to be_redirect
expected `#<ActionDispatch::TestResponse:0x00007fbe5fde51f0 #mon_owner=nil, #mon_count=0, #mon_mutex=#<Thread::...ch::Http::Headers:0x00007fbe5fdde9e0 #req=#<ActionController::TestRequest:0x00007fbe5fde5358 ...>>>>.redirect?` to return true, got false
# ./spec/controllers/search_controller_spec.rb:86:in `block (3 levels) in <top (required)>'
I am using devise gem to authenticate clients.
The tests are as follows:
describe SearchController do
before(:each) do
#client = FactoryGirl.create(:client)
end
describe "authenticated search" do
# a bunch of tests
end
describe "unauthenticated search" do
it "requires a user to be authenticated" do
get :search, params: { q: "tec" }, format: :json
expect(response).to be_redirect # FAILING HERE
end
end
end
If I run the test manually and go to /search?q=tec I get redirected to the sign_in page. The search_controller.rb has a before_action :authenticate_client!
I tried adding sign_out #client before the search but it didn't work.
Also tried current_client.reload but didn't recognize current_client.
In the authenticated search tests there is a call to stub_authenticate_client that has the following code:
def stub_authenticate_client(client)
allow(request.env['warden']).to receive(:authenticate!) { client }
allow(controller).to receive(:current_client) { client }
end
in case that is useful to solve this issue.
I also tried creating a stub_logout_client method like this:
def stub_logout_client(client)
allow(request.env['warden']).to receive(:authenticate!) { nil }
allow(controller).to receive(:current_client) { nil }
end
and calling it at the beginning of the test, but it is still passing the before_action authenticate_client!
Also tried what it was suggested here, but didn't work
The search controller that is being tested:
class SearchController < ClientApplicationController
before_action :authenticate_client!
def search
limit = params[:limit] ? params[:limit].to_i : 10
query = params[:q].to_s.downcase.strip
results = {}
if params[:report]
results[:this_report] = Report.where("SOME QUERY")
end
render json: results
end
end
Thank you!
The problem is related to the be_redirect check. Changed the test to check for content in the response and that solved it, like this:
describe "unauthenticated search" do
it "requires a user to be authenticated" do
get :search, params: { q: "tec" }, format: :json
expect(response.body).to have_content("content from the page I render")
end
end
Related
I have this api endpoint wot get all the blogs from my database that works id the user pass an api_key. This works correctly and now I'm trying to testing this endpoint.
Routes:
Rails.application.routes.draw do
get 'blogs', to: 'blogs#index'
end
Blogs controller:
class BlogsController < ApplicationController
def index
if params[:api_key]
user = User.find_by(api_key: params[:api_key])
if user.present?
#blogs = Blog.all
return render json: #blogs, status: :ok
end
end
render json: { error: "Unauthorized!" }, status: :bad_request
end
end
I'm new to rspec and tests in general, I watched a couple videos and tutorials and this is what I have so far:
spec/requests/blogs_spec.rb
require 'rails_helper'
RSpec.describe 'Blogs API', type: :request do
let!(:blogs) { Blog.limit(10) }
describe 'GET /blogs' do
before { get '/blogs' }
it 'returns status code 400' do
expect(response).to have_http_status(400)
end
context 'when the request is valid' do
before { get '/blogs', params: { api_key: '123123'} }
it 'returns status code 400' do
expect(response).to have_http_status(200)
end
end
end
end
I can't seem to make the last test work and I don't know why. My guess is that I'm not passing api_key correctly, but I don't know how
1) Blogs API GET /blogs when the request is valid returns status code 400
Failure/Error: expect(response).to have_http_status(200)
expected the response to have status code 200 but it was 400
# ./spec/requests/blogs_spec.rb:28:in `block (4 levels) in <top (required)>'
Ok, so accordingly to your question + comments, I can assume you are running your tests within test environment, but you are expecting to find a User existing in development database.
FactoryBot
You might wanna use FactoryBot to create records for your testing suite.
Add to your Gemfile:
group :development, :test do
gem 'factory_bot_rails'
end
In rails_helper.rb, add:
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
Now you should create your User factory. Create a new file spec/factories/user.rb with the following:
FactoryBot.define do
factory :user do
api_key { '123123' }
# You should define every any other required attributes here so record can be created
end
end
Finally, in your spec file:
....
context 'when the request is valid' do
before { get '/blogs', params: { api_key: user.api_key} }
let!(:user) { create(:user) }
it 'returns status code 200' do
expect(response).to have_http_status(200)
end
end
...
Now your test should pass. Notice that in testing database there is no Blog created also, so:
let!(:blogs) { Blog.limit(10) }
Will return an empty array. You will need to create a Blog factory too, and create blogs like:
let!(:blogs) { create_list(:blog, 2) }
Bonus
As soon as you start improving your tests, you may wanna take a look at Faker and Database Cleaner for ActiveRecord
I'm following this tutorial for a rails API but it is a little outdated and some things don't seem to work with newer versions of rails. I'm having a hard time with the user controller specs:
user_controller_spec.rb
require 'rails_helper'
RSpec.describe Api::V1::UsersController, type: :controller do
describe "GET #show" do
before(:each) do
#user = FactoryGirl.create :user
get :show, params: {id: #user.id}
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_name: true)
expect(user_response[:email]).to eql #user.email
end
it { expect(response).to have_http_status(200) }
end
end
user_controller.rb
class Api::V1::UsersController < ApplicationController
def show
render json: User.find(params[:id])
end
end
user.rb factory
FactoryGirl.define do
factory :user do
email { FFaker::Internet.email }
password "12345678"
password_confirmation "12345678"
end
end
But, this isn't working, the email doesn't seem to match. Any ideas what could be wrong?
Failures:
1) Api::V1::UsersController GET #show returns the information about a reporter on a hash
Failure/Error: expect(user_response[:email]).to eql #user.email
expected: "mitzie_nikolaus#rice.com"
got: nil
(compared using eql?)
# ./spec/controllers/api/v1/users_controller_spec.rb:12:in `block (3 levels) in <top (required)>'
The code is correct, but you've made a typo in using the symbolize_names option for JSON.parse.
I assume, that because you do not copy-paste examples, but type it by your own, which is great, because it's better for learning.
To fix the test just correct this line (change symbolize_name to symbolize_names):
user_response = JSON.parse(response.body, symbolize_names: true)
I am trying to validate that the current_user's organization matches that of the organization they are trying to view.
Here's the part of the controller that's failing this test (#organization is being defined in an earlier method):
if current_user.organization != #organization
redirect_to root_path, notice: "Not authorized to edit this organization"
end
Here's the failing test:
require 'rails_helper'
RSpec.describe Admin::PagesController, :type => :controller do
describe 'GET #home' do
login_user
before do
#organization = FactoryGirl.create(:organization)
end
context "valid params" do
it "renders the home template and returns http 200" do
get :home, name: #organization.name
expect(response).to render_template("home")
expect(response.status).to eq(200)
end
end
end
Here's my factory:
factory :user do
email { Faker::Internet.email }
organization_id 1
password "foobarfoobar"
password_confirmation { |u| u.password }
end
...And here's where login_user is being defined:
module ControllerMacros
def login_user
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
sign_in user
end
end
Stacktrace:
1) Admin::PagesController GET #home valid params renders the home template and returns http 200
Failure/Error: it "renders the home template and returns http 200" do
expecting <"home"> but rendering with <[]>
# ./spec/controllers/admin/pages_controller_spec.rb:15:in `block (4 levels) in <top (required)>'
However:
[2] pry(#<RSpec::ExampleGroups::AdminPagesController::GETHome::ValidParams>)> subject.current_user.organization == #organization
=> true
Not sure what is going wrong here, seems like pretty standard stuff. Any ideas?
Turns out the issue was that I was sending in the wrong parameter - should have been sending #organization.subdomain, not #organization.name. :(
doorkeeper's spec with token through http header
My goal is creating secure API between iOS and Rails4. So I've been trying doorkeeper-gem for a while. But I'm wasting time for testing and configuration now. In detail, the problem is doorkeeper_for method and token transferring through HTTP header. How using http request parameter was successful, but sending token through parameter is not good. So I want to send token with HTTP header, but doorkeeper does not see where request.header["My_TOKEN_PLACE"].
Circumstance
Now, I have api_controller.rb, communities_controller.rb, communities_controller_spec.rb, doorkeeper.rb.
api_controller.rb
class ApiController < ApplicationController
before_action :create_token
doorkeeper_for :all, scopes: [:app]
respond_to :json, handler: :jbuilder
private
def error_handle
raise 'Failed.'
end
def create_token
params[:access_token] = request.headers["HTTP_AUTHENTICATION"]
# this does not read by doorkeeper
end
end
and communities_controller.rb
class CommunitiesController < ApiController
def show
#community = Community.find params[:id]
end
def search
Query.create q: #search_form.q if #search_form.q.present?
community_search = Community.search title_or_description_cont: #search_form.q
#communities = community_search.result(distinct: true).page params[:page]
end
end
and communities_controller_spec.rb
require 'spec_helper'
describe CommunitiesController do
let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "http://app.com") }
let(:user){ create :user }
let!(:access_token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "app" }
before(:each) do
request.env["HTTP_ACCEPT"] = 'application/json'
end
describe "#show" do
let(:community) { create :community }
before do
request.headers["HTTP_AUTHENTICATION"] = access_token.token
get :show, id: community
end
it { expect(response).to be_success }
it { expect(response.status).to be 200 }
end
describe "#search" do
before { get :search, access_token: access_token.token }
it { expect(response).to be_success }
it { expect(response.status).to be 200 }
end
end
and config/initializer/doorkeeper.rb
Doorkeeper.configure do
orm :active_record
resource_owner_authenticator do
User.find id: session[:user_id]
default_scopes :app
end
and result of rspec communities_controller.rb is here.
/Users/shogochiai/Documents/anyll% be rspec spec/controllers/communities_controller_spec.rb
FF..
Failures:
1) CommunitiesController#show should be success
Failure/Error: it { expect(response).to be_success }
expected success? to return true, got false
# ./spec/controllers/communities_controller_spec.rb:20:in `block (3 levels) in <top(required)>'
2) CommunitiesController#show should equal 200
Failure/Error: it { expect(response.status).to be 200 }
expected #<Fixnum:401> => 200
got #<Fixnum:803> => 401
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
`expect(actual).to eq(expected)` if you don't care about
object identity in this example.
# ./spec/controllers/communities_controller_spec.rb:21:in `block (3 levels) in <top(required)>'
Finished in 0.33439 seconds
4 examples, 2 failures
Failed examples:
rspec ./spec/controllers/communities_controller_spec.rb:20 # CommunitiesController#show should be success
rspec ./spec/controllers/communities_controller_spec.rb:21 # CommunitiesController#show should equal 200
Randomized with seed 18521
It seems
before do
request.headers["HTTP_AUTHENTICATION"] = access_token.token
get :show, id: community
end
is not authenticated
and
before { get :search, access_token: access_token.token }
is authenticated.
Supplementary
I did pp debug in controller, pp request and pp responce result had have a pair of key-value that "HTTP_AUTHENTICATION": "xrfui24j53iji34.....(some Hash value)".
Here my http basic authentication in the application controller file (application_controller.rb)
before_filter :authenticate
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "username" && password == "password"
end
end
and the default test for the index action of my home controller (spec/controllers/home_controller_spec.rb)
require 'spec_helper'
describe HomeController do
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
Test doesn't run because of the authentication method. I could comment "before_filter :authenticate" to run them but I would like to know if there is way to make them worked with the method.
Thank you!
Update (2013): Matt Connolly has provided a GIST which also works for request and controller specs: http://gist.github.com/4158961
Another way of doing this if you have many tests to run and don't want to include it everytime (DRYer code):
Create a /spec/support/auth_helper.rb file:
module AuthHelper
def http_login
user = 'username'
pw = 'password'
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
end
end
In your test spec file:
describe HomeController do
render_views
# login to http basic auth
include AuthHelper
before(:each) do
http_login
end
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
end
Credit here - Archived site
Sorry I didn't seek enough, the solution seems to be the following:
describe "GET 'index'" do
it "should be successful" do
#request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password")
get 'index'
response.should be_success
end
end
Some answers suggest to set request.env which is unsafe, because request can be nil and you will end up with private method env' called for nil:NilClass, especially when run single tests with rspec -e
Correct approach will be:
def http_login
user = 'user'
password = 'passw'
{
HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,password)
}
end
get 'index', nil, http_login
post 'index', {data: 'post-data'}, http_login
For me, with Rails 6, I need keyword arguments for rspec get method like .. get route, params: params, headers: headers
Auth Helper method
module AuthHelper
def headers(options = {})
user = ENV['BASIC_AUTH_USER']
pw = ENV['BASIC_AUTH_PASSWORD']
{ HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,pw) }
end
def auth_get(route, params = {})
get route, params: params, headers: headers
end
end
and the rspec request test.
describe HomeController, type: :request do
include AuthHelper
describe "GET 'index'" do
it "should be successful" do
auth_get 'index'
expect(response).to be_successful
end
end
end
When using Rspec to test Grape APIs, the following syntax works
post :create, {:entry => valid_attributes}, valid_session
where valid_session is
{'HTTP_AUTHORIZATION' => credentials}
and
credentials = ActionController::HttpAuthentication::Token.encode_credentials("test_access1")
These are great solutions for controller and request specs.
For feature tests using Capybara, here is a solution to make HTTP Basic authentication work:
spec/support/when_authenticated.rb
RSpec.shared_context 'When authenticated' do
background do
authenticate
end
def authenticate
if page.driver.browser.respond_to?(:authorize)
# When headless
page.driver.browser.authorize(username, password)
else
# When javascript test
visit "http://#{username}:#{password}##{host}:#{port}/"
end
end
def username
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_username
end
def password
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_password
end
def host
Capybara.current_session.server.host
end
def port
Capybara.current_session.server.port
end
end
Then, in your spec:
feature 'User does something' do
include_context 'When authenticated'
# test examples
end
My solution:
stub_request(method, url).with(
headers: { 'Authorization' => /Basic */ }
).to_return(
status: status, body: 'stubbed response', headers: {}
)
Use gem webmock
you can tighten verification by change:
/Basic */ -> "Basic #{Base64.strict_encode64([user,pass].join(':')).chomp}"
URL - can be a regular expression