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/
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 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 have started rspec coding recently and i am new to rails framework, rspec fails where i am using 'current_user' in controller. Please check below for my controller and rspec code. Thanks in advance.
Controller code:
def task
#tasks = current_user.alerts.where(kind: "TASK")
end
rspec code:
describe "get #task" do
it "assigns a task" do
sign_in(#user)
get :task
expect(response).to have_http_status(200)
end
You can do it like this:
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user) # Don't forget to create a factory for user
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
It is better to put it in support/devise.rb:
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
end
end
end
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
you can say login_user instead of sign_in(#user)
I'm trying to test my controller for access from unregistered users. Im using devise (3.3.0) and rspec (3.0.0).
spec/controllers/dares_controller_spec.rb
require 'rails_helper'
describe DaresController do
let(:challenger) { create(:user) }
let(:acceptor) { create(:user) }
let(:challenge) { create(:challenge) }
let(:dare) { create(:dare) }
let(:user) { create(:user) }
describe 'Guest access to dares' do
describe 'GET #show' do
it "redirects to root" do
get :show, id: dare.id, challenge_id: challenge.id
expect(response).to require_login
end
end
end
end
In the controller:
dares_controller.rb
before_action :authenticate_user!
def show
end
I get the following error:
Failures:
1) DaresController Guest access to dares GET #show redirects to root
Failure/Error: get :show, id: dare.id, challenge_id: challenge.id
NoMethodError:
undefined method `authenticate!' for nil:NilClass
# ./spec/controllers/dares_controller_spec.rb:16:in `block (4 levels) in <top (required)>'
I tried adding
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
to my spec_helper / rails_helper but it didnt fix the problem. I googled for solutions for a few hours, bothing seems to help.
Matcher - require_login
RSpec::Matchers.define :require_login do |expected|
match do |actual|
expect(actual).to redirect_to Rails.application.routes.url_helpers.new_user_session_path
end
failure_message do |actual|
"expected to require login to access the method"
end
failure_message_when_negated do |actual|
"expected not to require login to access the method"
end
description do
"redirect to the login form"
end
end
change this line:
describe DaresController do
to this one:
RSpec.describe DaresController, type: :controller do
Since you configure the spec_helper or rails_helper with:
config.include Devise::TestHelpers, type: :controller
You should set the correct spec type to works properly. It's a rspec behavior in rspec~3