I am writing a rails application with devise and testing in rspec. I have an issue where my rspec fails the user_authenticate when the user is not logged in. All of my specs pass except for the last one- the error it gives is
"Failure/Error: get :show, id: course NoMethodError:undefined method `authenticate' for nil:NilClass"
I suspect I am having this issue because I have a before_action :authenticate_user! call and for someone not logged in, it tries to authenticate nil. Is there a way to make it fail gracefully and redirect to user_session? I tried to create an inherited version of authenticate_user to do the redirect, but it does not appear to work. I know this is probably a noob question but I have extensively searched around without any solution. thanks!
This is my controller:
class CoursesController < ApplicationController
before_action :authenticate_user!, except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
def authenticate_user!
if user_signed_in?
super
else
redirect_to user_session
end
end
end
This is my spec:
require 'rails_helper'
RSpec.describe CoursesController, :type => :controller do
describe "user access " do
before(:each) do
#user = create(:user)
#request.env['devise.mapping'] = Devise.mappings[:user]
sign_in :user, #user
end
describe 'GET #index' do
it 'renders the :index view' do
get :index
expect(response).to render_template :index
end
end
describe 'GET #show' do
it 'assigns the requested course to #course' do
course = create(:course)
get :show, id: course
expect(assigns(:course)).to eq course
end
it 'renders the :show template' do
course = create(:course)
get :show, id: course
expect(response).to render_template :show
end
end
end
describe "guest access " do
describe 'GET #index' do
it 'renders the :index view' do
get :index
expect(response).to render_template :index
end
end
describe 'GET #show' do
it 'redirects to the login url' do
course = create(:course)
get :show, id: course
expect(response).to redirect_to 'user_session'
end
end
end
end
It seems that devise does the redirect to "users#sessions" itself when you add :authenticate_user! to the show action for a guest or a user that is not signed in.
Try removing your custom :authenticate_user! method and add "only: [:show]" to your before_action
class CoursesController < ApplicationController
before_action :authenticate_user!, only: [:show], except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
end
Update
class CoursesController < ApplicationController
before_action :authenticate_user!, except: [:index]
before_action :set_course, only: [:show]
def index
#course = Course.order('name')
end
def show
if user_signed_in?
render :show
else
redirect_to user_session
end
end
private
def set_course
#course = Course.find(params[:id])
end
def course_params
params.require(:course).permit(:name,:description,:department,:hidden,
:lecture_attributes => [:name,:description,:level])
end
end
This isn't a super satisfying result but it appears as if authenticate_user! does not properly work with rspec. When I load the page directly, it correctly redirects to the login page, I am still interested to know what the proper work around is.
Can I have some sort of OR statement where I can first check if a user exists? There must be a standard way to deal with this problem so I can ensure my app is properly redirecting.
Related
I'm working on a group project building a chess application & trying to understand why I am getting a routing error when running this test.
I am trying to test the update action in my pieces controller, to make sure that it redirects once a piece is moved. Bare in mind I am new to testing and am guessing that there is a problem with how I've written this one. Really stuck and could use an explanation of what I am missing.
controller:
class PiecesController < ApplicationController
before_action :authenticate_user!, only: [:show, :update]
def show
#show the board again
#game = Game.find(params[:id])
#pieces = #game.pieces
end
def update
#piece = Piece.find(params[:id])
#game = #piece.game
if #piece.update_attributes(piece_params)
redirect_to game_path(#game)
end
end
private
def piece_params
params.require(:piece).permit(:x_position, :y_position)
end
end
spec:
require 'rails_helper'
RSpec.describe PiecesController, type: :controller do
describe 'update action' do
it 'should redirect to game path when piece is moved' do
user1 = FactoryGirl.create(:user)
game = FactoryGirl.create(:game, white_user_id: user1.id )
piece = FactoryGirl.create(:piece, game: game, user_id: user1.id, x_position: 0, y_position: 0)
put :update, params: {x_position: 1, y_position: 1}
expect(response).to redirect_to game_path(game)
end
end
end
routes:
Rails.application.routes.draw do
devise_for :users
root 'static_pages#index'
resources :games do
member do
patch :join
end
resources :pieces, only: [:show, :update]
end
end
You're missing id on put :update, params: {x_position: 1, y_position: 1}?
You probably want something like:
put :update, id: piece.id, game_id: game.id, piece: {x_position: 1, y_position: 1}
On RSpec, the destroy action fails in ActionController::InvalidAuthenticityToken.
I don't think this is a problem as skip_before_filter :verify_authenticity_token, :only => [:index, :show] in my Controller is enough to fix the problem, but it still bothers me that this has to happen and it seems like it could be a bad security practice.
-
Here's the would-be-failing RSpec test:
require 'spec_helper'
describe "Products" do
subject { page }
...
describe "Destroy" do
before {
FactoryGirl.create(:product)
visit products_path
}
it "should have a destroy link" do
expect { should have_link('Destroy') }
end
it "link should destroy" do
expect do
click_link('Destroy', match: :first)
end.to change(Product, :count).by(-1)
end
end
end
-
And here's the controller:
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
skip_before_filter :verify_authenticity_token, :only => [:destroy] #destroy wasn't working without this
def index
#products = Product.all
end
def show
end
def new
#product = Product.new
end
def edit
end
def create
...
end
def update
...
end
def destroy
#product.destroy
flash[:notice] = "Product was successfully destroyed."
redirect_to products_path
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:title, :description, :image_url, :price)
end
end
-
Ideas?
You are correct, skipping your authenticity token check is a bad idea. At the very least, ensure you are only doing that for your test environment:
skip_before_filter :verify_authenticity_token, :only => [:destroy] if Rails.env.test?
The issue with invalid tokens when you run tests may be the result of running your tests in your development environment.
Ensure your test_helper.rb file is correctly setting the environment:
Rails.env = 'test'
This works better than the ENV['RAILS_ENV'] = 'test' style approach.
Best of luck! Did you happen to find any other resolution?
I'm making an app where people can create jobs, but can only destroy the jobs they've created. I know that my app lets people destroy jobs because I can test it manually, but RSpec is lagging behind.
Here's the relevant test:
jobs_controller_spec.rb
require 'spec_helper'
describe JobsController do
let!(:user) { FactoryGirl.create(:user) }
let!(:job) { FactoryGirl.create(:job, user: user) }
let!(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
let!(:wrong_job) { FactoryGirl.create(:job, user: wrong_user) }
[...]
describe "correct user control" do
before { sign_in user }
describe "users can only delete their own jobs" do
it "should not change job count" do
expect do
delete :destroy, id: wrong_job.id
end.to_not change(Job, :count)
end
end
describe "users can delete their own jobs" do
it "should decrease job count" do
expect do
delete :destroy, id: job.id
end.to change(Job, :count).by(-1)
end
end
end
end
Here's the failing test:
1) JobsController correct user control users can delete their own jobs should decrease job count
Failure/Error: expect do
count should have been changed by -1, but was changed by 0
# ./spec/controllers/jobs_controller_spec.rb:41:in `block (4 levels) in <top (required)>'
jobs_controller.rb
class JobsController < ApplicationController
skip_before_action :require_signin, only: [:index, :show]
skip_before_action :correct_user, only: [:index, :show, :new, :create]
before_action :set_job, only: [:show, :edit, :update, :destroy]
[...]
def destroy
#job.destroy
respond_to do |format|
format.html { redirect_to jobs_url }
format.json { head :no_content }
end
end
private
def set_job
#job = Job.find(params[:id])
end
def job_params
params.require(:job).permit(:title, :org, :internship, :postdate, :filldate, :location, :link, :description)
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_filter :require_signin
before_filter :correct_user
include SessionsHelper
private
def require_signin
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#job = current_user.jobs.find_by(id: params[:id])
redirect_to root_url if #job.nil?
end
end
rake routes
Prefix Verb URI Pattern Controller#Action
jobs GET /jobs(.:format) jobs#index
POST /jobs(.:format) jobs#create
new_job GET /jobs/new(.:format) jobs#new
edit_job GET /jobs/:id/edit(.:format) jobs#edit
job GET /jobs/:id(.:format) jobs#show
PATCH /jobs/:id(.:format) jobs#update
PUT /jobs/:id(.:format) jobs#update
DELETE /jobs/:id(.:format) jobs#destroy
[...]
As Peter Alfvin points out, if there is authentication in controller specs you have to setup a session and pass it as third parameter, for example:
...
let(:some_user) { User.create }
def valid_session
{ user_id: some_user.id }
end
describe "DELETE destroy" do
it "destroys the requested job" do
job = Job.create! valid_attributes
expect {
delete :destroy, { :id => job.to_param }, valid_session
}.to change(Job, :count).by(-1)
end
end
The solution is to pass no_capybara: true to sign_in. Capybara doesn't work with controller tests, so one can't use capybara to manage the sign in process.
Thanks to Patrick Brinich-Langlois for the solution.
I used the M. Hartl Rails Tutorial to create my app. So I have a User model, and all current_user and signed_in_user methods.
I want to make the following test pass :
describe "submitting a PATCH request to the Users#update action" do
before do
be_signed_in_as FactoryGirl.create(:user)
patch user_path(FactoryGirl.create(:user))
end
specify { expect(response).to redirect_to(root_path) }
end
But the test fails :
Failure/Error: specify { expect(response).to redirect_to(root_path) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/signin>.
Expected "http://www.example.com/" to be === "http://www.example.com/signin".
So here is a part of the User controller
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
.
.
.
.
private
def signed_in_user
unless !current_user.nil?
store_url
redirect_to signin_url, notice: t('sign.in.please')
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
If I remove the before_create :signed_in_user... line, the test pass.
But why is that ? The be_signed_in_as spec method is working in all the others test (~ 1k) so the reason must lie within the specify { expect(response) thing.
Your test is going to a user_path for a different user than you're logging in as, so you're getting redirected to the root by your correct_user filter. You need to save the user that you're logging in as and use that for your user_path.
Each time you call FactoryGirl.create(:user), you're creating an additional user. The code you list is creating two separate user records in the database. So unless you're intending on creating two different users for this test, you should probably have a line before the before block like:
let(:user) { FactoryGirl.create(:user) }
Then just refer to user everywhere you want the one user record.
I can't get my tests to work and was wondering if anyone have any pointers. I am testing my user edit page such that you have to be logged in to be able to see and edit the user profile. Following the authlogic documentation, here are the relevant codes:
class ApplicationController < ActionController::Base
helper_method :current_user_session, :current_user
private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.user
end
def require_current_user
unless current_user
flash[:error] = 'You must be logged in'
redirect_to sign_in_path
end
end
end
class UsersController < ApplicationController
before_filter :require_current_user, :only => [:edit, :update]
...
end
In my users_controller_spec
describe "GET 'edit'" do
before(:each) do
#user = Factory(:user)
UserSession.create(#user)
end
it "should be successful" do
# test for /users/id/edit
get :edit, :id => #user.id
response.should be_success
end
I tested it using the browser and it works. You have to be logged in to be able to edit your profile but it doesn't work in my rspec test.
I have a feeling it has something to do with mock controller but I can't figure out how. I've also read the test case but still can't get it to work.
Please help and much thanks!!!
You can stub the current_user method on the ApplicationController like so:
fake_user = controller.stub(:current_user).and_return(#user) #you could use a let(:user) { FactoryGirl.create(:user) } instead of before_each
get :edit, :id => fake_user.id
response.should be_success