I'm writing RSpec tests for my SessionsController. My tests work fine when testing session#create with valid credentials. However, I want to also write tests for what happens when the users credentials are invalid, such as redirecting back to the sign in page, setting a flash alert, etc. But for any of these tests, I'm getting an error:
1) SessionsController POST #create when password is INCORRECT
Failure/Error: post :create, user: {username: 'Example', password: ''}
ArgumentError:
uncaught throw :warden
# ./spec/support/default_params.rb:7:in `process_with_default_params'
# ./spec/controllers/sessions_controller_spec.rb:24:in `block (4 levels) in <top (required)>'
Here's my sessions_controller_spec.rb code:
require 'spec_helper'
describe SessionsController do
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
end
describe 'POST #create' do
context "when password is INCORRECT" do
let!(:user) { FactoryGirl.create(:user, username: 'Example', password: 'Secr3t&$') }
before(:each) do
post :create, user: { username: 'Example', password: '' }
end
it { should set_the_flash[:alert].to('Invalid username or password.') }
it { should respond_with(:redirect) }
it { should redirect_to(:new_user_session) }
end
end
Here's my spec_helper.rb code:
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
end
Any help would be much appreciated. Thanks!
I know it is too late now but in case this helps someone else: you need to add setup_controller_for_warden to your before block, so it becomes:
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
setup_controller_for_warden
end
Also it would be better to use the translation for you assertion. In rspec 3 format, the assertion should like this:
expect(flash[:alert]).to eq(I18n.t('devise.failure.invalid'))
Related
I have a helper method that is using #request.env and Devise to login the user:
def login_user(user)
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
end
I'm trying to write a feature spec where I need to login, but login_user is failing:
1) Search finds a manufacturer
Failure/Error: #request.env["devise.mapping"] = Devise.mappings[:user]
NoMethodError:
undefined method `env' for nil:NilClass
# ./spec/support/controller_macros.rb:3:in `login_user'
# ./spec/features/search_spec.rb:17:in `block (2 levels) in <top (required)>'
How can I fix? I have no experience with feature specs, with cucumber I'd use a feature to login, I'm definitely not sure that's the best practice with rspecs.
Thanks in advance.
In Capybara feature specs in my app, we use the Warden test helpers:
# spec/rails_helper.rb
RSpec.configure do |config|
config.include Warden::Test::Helpers
Warden.test_mode!
end
# in the feature spec
login_as(user, scope: :user)
Also, for controller specs:
allow(controller).to receive(:current_user).and_return(user)
I was working with different library then Devise but it should works. It's very simple mock:
allow_any_instance_of(ApplicationController).to receive(:current_user) { user }
You can make it even simpler by making a special helper:
# spec/support/feature_spec_helper.rb`
module FeatureSpecHelper
def login(user)
allow_any_instance_of(ApplicationController).to receive(:current_user) { user }
end
end
Then in spec config (spec_helper or rails_helper) drop anywhere
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
# (...)
RSpec.configure do |config|
config.include FeatureSpecHelper, :type => :feature
# (...)
next you can use in feature spec login user
You can use Capybara as well to connect your test user. In my rails_helper I have this method:
def create_user_and_log_in
create :user, email: 'user#test.com', password: 'password'
visit new_user_session_path
fill_in :user_email, with: 'user#test.com'
fill_in :user_password, with: 'password'
click_on 'Connexion'
end
You can then call this method in your specs.
I'm looking for the way to do this but in request specs. I need to log in and log out a double or instance_double to Devise instead of an actual ActiveModel/ActiveRecord.
By using the code in the wiki page:
module RequestSpecHelpers
def sign_in(user = double('user'))
if user.nil?
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
allow(controller).to receive(:current_user).and_return(nil)
else
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
end
end
I get this error: undefined method 'env' for nil:NilClass
I saw this question and this wiki, but if I want to use doubles of the user those two don't work. I was using the last one, works fine with a real user but with a double it doesn't log it in.
The tests:
RSpec.describe 'new shipment', type: :request do
describe 'authenticated as user' do
before do
#user = double(:user, id: 1, email: 'user#gmail.com', password: 'password',
id_card: '4163649-1', first_name: 'Jane', last_name: 'Doe')
sign_in #user
end
end
end
If I include:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :requests
end
I get this error:
Failure/Error: #request.env['action_controller.instance'] = #controller
NoMethodError:
undefined method `env' for nil:NilClass
# /root/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:40:in `setup_controller_for_warden'
Problem with Frederick Cheung answer
If I do that the login_asmethod doesn't fail but it doesn't really log the user in. So when I try to access a path that has a before_action :authenticate_user! callback it fails.
Here is my code based on his answer:
require 'rails_helper'
RSpec.describe 'new shipment', type: :request do
describe 'authenticated as user' do
include Warden::Test::Helpers
before(:each) do
Warden.test_mode!
#stub more methods as needed by the pages you are testing
user = instance_double(User, to_key: 1, authenticatable_salt: 'example')
login_as(user, scope: 'user')
end
it 'returns 200 Ok' do
get new_shipment_path
expect(response).to have_http_status(:ok)
end
end
end
And this is the response when running rspec:
1) new shipment authenticated as user returns 200 Ok
Failure/Error: expect(response).to have_http_status(:ok)
expected the response to have status code :ok (200) but it was :found (302)
# ./spec/requests/shipments_requests_spec.rb:41:in `block (3 levels) in <top (required)>'
As you can see instead of allowing me to access the path it redirects me, this is the usual behavior when the user is not allowed to access the path.
It I change the instance_double for a real User saved in the database this approach works correctly:
# only changed this line in the before hook
user = User.create(email: 'user#gmail.com', password: 'password',id_card: '4163649-1', first_name: 'Jane', last_name: 'Doe')
Result:
Finished in 3.23 seconds (files took 33.47 seconds to load)
1 example, 0 failures
It sounds like you're using Devise 3.x ( since Devise::TestHelpers was renamed in devise 4), Devise::TestHelpers is only designed to work with controller specs.
If you can upgrade to devise 4, it has separate helpers for request specs and controller tests. This is just a very thin wrapper around what warden provides, which hides all the messing around with env.
There are some extra complications when using a double - you need to stub out various methods devise calls that you might not realise.
The following worked for me
describe 'example' do
include Warden::Test::Helpers
before(:each) do
Warden.test_mode!
#stub more methods as needed by the pages you are testing
user = instance_double(User, to_key: 1, authenticatable_salt: 'example')
login_as(user, scope: 'user')
end
end
I'm trying to follow Mike Hartl's tutorial with RSpec. I've reached the password reset integration test, and so far I was doing well. Then I got to the line that said user = assigns(:user)
I searched for an answer so here is what I have and my error.
Failures:
PasswordResets Password resets email input valid email sends password and redirects to root
Failure/Error: expect(assigns(:user)).to eq([user])
NameError:
undefined local variable or method `user' for #<RSpec::ExampleGroups::PasswordResets::PasswordResets::EmailInput:0x007fc5d16b0ca8>
# ./spec/requests/password_resets_spec.rb:26:in `block (4 levels) in <top (required)>'
require 'rails_helper'
RSpec.describe "PasswordResets", type: :request do
describe "Password resets" do
before do
ActionMailer::Base.deliveries.clear
#valid_user = create(:user)
end
describe "unsuccessful password reset" do
it "flashes danger when email is blank" do
get new_password_reset_path(#valid_user.id)
post password_resets_path, params: { password_reset: { email: " " } }
expect(flash[:danger]).to be_present
expect(page).to render_template(:new)
end
end
describe "email input" do
it "valid email sends password and redirects to root" do
post password_resets_path, params: { password_reset: { email: #valid_user.email } }
expect(#valid_user.reset_digest).not_to match (#valid_user.reload.reset_digest)
expect(ActionMailer::Base.deliveries.size).to eq(1)
expect(flash[:info]).to be_present
expect(page).to redirect_to root_url
expect(assigns(:user)).to eq([user])
end
end
end
end`
The tutorial I'm following https://www.railstutorial.org/book/password_reset 12.18
I'm not sure what else to do.
Your test is erroring because you haven't defined a user variable at that point in your test. You have defined #valid_user. My guess is your test would pass if you change:
expect(assigns(:user)).to eq([user])
to
expect(assigns(:user)).to eq(#valid_user)
I override SessionsController in my own controller and tried to test with RSpec. Fist, I setup devise with
#request.env["devise.mapping"] = Devise.mappings[:user]
my spec:
require 'rails_helper'
require 'authentication_helper'
RSpec.describe Users::SessionsController, type: :controller do
include AuthenticationHelper
describe 'create new session' do
before(:each) do
setup_auth
end
let(:user) {FactoryGirl.create(:user, username: 'john', email: 'john#gmail.com', password: 'pwd1234')}
it 'should return 200 with valid username and password' do
post :create, user: {login: 'john', password: 'pwd1234'}
expect(response).to have_http_status 200
expect(controller.current_user.id).to eq(user.id)
end
end
end
my SessionsController just return http 401 or http 200.
When I run my spec, I get this error:
NoMethodError:
undefined method authenticate?' for nil:NilClass
# /usr/local/bundle/gems/devise-3.5.6/app/controllers/devise_controller.rb:103:inrequire_no_authentication'
# ./spec/controllers/users/sessions_controller_spec.rb:16:in block (3 levels) in <top (required)>'
# ./spec/rails_helper.rb:45:inblock (3 levels) in '
# /usr/local/bundle/gems/database_cleaner-1.6.0/lib/database_cleaner/generic/base.rb:16:in cleaning'
# /usr/local/bundle/gems/database_cleaner-1.6.0/lib/database_cleaner/base.rb:98:incleaning'
# /usr/local/bundle/gems/database_cleaner-1.6.0/lib/database_cleaner/configuration.rb:86:in block (2 levels) in cleaning'
# /usr/local/bundle/gems/database_cleaner-1.6.0/lib/database_cleaner/configuration.rb:87:incall'
# /usr/local/bundle/gems/database_cleaner-1.6.0/lib/database_cleaner/configuration.rb:87:in cleaning'
# ./spec/rails_helper.rb:44:inblock (2 levels) in '
What am I doing wrong?
You know that Devise offers RSpec test helpers for controller specs. However, in request specs, they will not work.
Here is a solution for request specs, adapted from the Devise wiki. We will simply use Warden's test helpers – you probably already load them for your Cucumber tests.
First, we define sign_in and sign_out methods. These will behave just like those you know from controller specs:
module DeviseRequestSpecHelpers
include Warden::Test::Helpers
def sign_in(resource_or_scope, resource = nil)
resource ||= resource_or_scope
scope = Devise::Mapping.find_scope!(resource_or_scope)
login_as(resource, scope: scope)
end
def sign_out(resource_or_scope)
scope = Devise::Mapping.find_scope!(resource_or_scope)
logout(scope)
end
end
Finally, load that module for request specs:
RSpec.configure do |config|
config.include DeviseRequestSpecHelpers, type: :request
end
Done. You can now write request specs like this:
sign_in create(:user, name: 'John Doe')
visit root_path
expect(page).to include('John Doe')
Reference:
https://makandracards.com/makandra/37161-rspec-devise-how-to-sign-in-users-in-request-specs
You have to stub out the warden.authenticate! call, not just the current_user method.
For login success test cases:
before(:each) do
# assuming `user` is defined and returns a User instance
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
For failure cases:
before(:each) do
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, scope: :user)
allow(controller).to receive(:current_user).and_return(nil)
end
This works for me in devise 4.x. Found this in https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs
You should have a helper for the sign in, for example
module AuthenticationHelpers
module Controller
def sign_in(user)
controller.stub(:current_user).and_return(user)
controller.stub(:user_id).and_return(user.id)
end
end
module Feature
def sign_in(user, options={})
visit "/users/sign_in"
fill_in "Email", with: user.email
fill_in "Password", with: options[:password]
click_button "Log in"
end
end
end
Any concerns, do not hesitate to comment
I've scoured SO and no one else's results seem to work for me:
I have a ControllerHelper method for my Spec based off what was suggested for Devise:
def login_existing_user
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
company = FactoryGirl.create(:company)
user.company_id = company.id
sign_in user
end
I am also creating a Company in this method since that's step 2 of the sign up process for a user to be able to get my authenticated homepage.
At this point, I'm just trying to log the user in with my ScansControllerSpec:
RSpec.describe ScansController, type: :controller do
before(:all) do
login_existing_user
#device = build(:device)
end
describe "GET #create" do
it "returns http success" do
get :create, :device_id => #device.id
puts response
# expect(response).to have_http_status(:success)
end
end
....
end
But I'm getting this for every one of my CRUD method RSpecs:
1) ScansController GET #create returns http success
Failure/Error: #request.env["devise.mapping"] = Devise.mappings[:user]
NoMethodError:
undefined method `env' for nil:NilClass
# ./spec/support/controller_helpers.rb:15:in `login_existing_user'
# ./spec/controllers/scans_controller_spec.rb:6:in `block (2 levels) in <top (required)>'
As other posts have suggested, I am including the Devise::TestHelpers in my rails_helper.rb file. I've also included my ControllerHelpers:
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.include Warden::Test::Helpers
Warden.test_mode!
config.infer_spec_type_from_file_location!
config.include ControllerHelpers, type: :controller
config.after do
Warden.test_reset!
end
end
In the end, I need to be able to log a user in to test that protected controller methods work. I'm not sure if this is an association problem, so I'll add that a user has to have a company, and a company has to have a subscription in order to successfully log in.
... but I can't even get that far since this error is holding me back.
Seemed to have gotten past this issue by following: http://willschenk.com/setting-up-testing/