Devise and Rspec - undefined method `authenticate!' for nil:NilClass - ruby-on-rails

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

Related

Rspec: Access a controller concern's methods in request specs

I have a controller concern defined as follows:
module Auth
extend ActiveSupport::Concern
private
def sign_in(user)
session[:customer_id] = user.id
self.current_user = user
end
def signed_in?
!current_user.nil?
end
...
# other methods
end
And this module is included in the ApplicationController:
class ApplicationController < ActionController::Base
include Auth
helper_method :signed_in?, :current_user, :current_user?
before_action :authorize
end
The questions I have:
should we really extend extend ActiveSupport::Concern in a concern? If so wy and when not?
how can I enable the concern module so that its methods could be re-used in RSpec request specs?
I tried to add the following in spec/support/utilities.rb:
include ApplicationHelper
include Auth
as well as required all the files from spec/support directory in rails_helper.rb and included the above modules:
# Require all the files in support directory
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
...
# include ApplicationHelper
config.include ApplicationHelper, type: :feature
config.include Auth, type: :request
...
end
Now, when running a requests/customers_spec.rb:
require 'rails_helper'
RSpec.describe "/customers", type: :request do
describe "GET /index" do
it "renders a successful response" do
customer = create(:customer)
sign_in(customer)
get customers_url
expect(response).to be_successful
end
end
...
it fails with:
Failures:
1) /customers GET /index renders a successful response
Failure/Error: session[:customer_id] = user.id
NoMethodError:
undefined method `session' for nil:NilClass
# ./app/controllers/concerns/auth.rb:7:in `sign_in'
# ./spec/requests/customers_spec.rb:10:in `block (3 levels) in <top (required)>'
What am I missing?
I'm using Rails 6.0.3, rspec-rails 4.0.1.
The solution I came to was to add the following method to the spec/support/login_macros.rb:
module LoginMacros
def sign_in_with_browser(user)
visit login_path
fill_in 'E-mail', with: user.email
fill_in 'Password', with: user.password
click_button 'Login'
end
def sign_out_with_browser
click_link 'Logout'
end
# to enable user authentication in request specs
def login_without_browser(user)
post login_url, params: { email: user.email, password: 'secret' }
end
end
Then test a protected end-point in the above request spec as follows:
describe "GET /index" do
it "renders a successful response" do
customer = create(:customer)
login_without_browser(customer)
get customers_url
expect(response).to be_successful
end
end

How to test Devise Sessions Controller with RSpec

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

Devise Rspec undefined method `env' for nil:NilClass

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/

Having probleman with ActionController::UrlGenerationError on RSPEC

Im writing some tests for my controller, but im getting the error below when i'm running rspec ./spec/controllers, when i just run the spec direct in the file i'm getting everything green.
1) VersionOne::UsersController GET #actives should return all actives
Failure/Error: get :actives
ActionController::UrlGenerationError:
No route matches {:action=>"actives", :controller=>"version_one/users"}
# ./spec/controllers/version_one/users_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
2) VersionOne::UsersController GET #archived should return all archives
Failure/Error: get :archives
ActionController::UrlGenerationError:
No route matches {:action=>"archives", :controller=>"version_one/users"}
# ./spec/controllers/version_one/users_controller_spec.rb:23:in `block (3 levels) in <top (required)>'
3) VersionOne::UsersController POST #create should create a new collaborator with success
Failure/Error: post :create, params: params
This is my controller
class UsersController < ApiControler
def actives
users = User.actives
render json: users
end
def archives
users = User.archiveds
render json: users
end
def create
user = User.build(user_params)
render json: user
end
end
This are my routes
scope 'api' do
scope 'v1', module: 'version_one' do
resources 'users' do
collection do
get 'actives'
get 'archives'
end
member do
match 'active'
match 'archive'
end
end
end
end
This are my tests
RSpec.describe VersionOne::UsersController, type: :controller do
before(:all) do
7.times { Collaborator.build(attributes_for(:user)) }
8.times { Admin.build(attributes_for(:user)) }
5.times { Collaborator.build(attributes_for(:user)).archived! }
end
describe "GET #actives" do
it "should return all actives" do
get :actives
body = JSON.parse(response.body)
expect(response).to have_http_status(:success)
expect(body["pagination"]["per_page"]).to eq(20)
expect(body["pagination"]["total_objects"]).to eq(15)
end
end
describe "GET #archived" do
it "should return all archives" do
get :archives
body = JSON.parse(response.body)
expect(response).to have_http_status(:success)
expect(body["pagination"]["per_page"]).to eq(20)
expect(body["pagination"]["total_objects"]).to eq(5)
end
end
describe "POST #create" do
it "should create a new collaborator with success" do
params = { kind: "Collaborator", user: { name: "User", email: "user#email.net", password: "123456", cpf: "36156291830" } }
post :create, params: params
body = JSON.parse(response.body)
expect(response).to have_http_status(:success)
expect(body).to include(
"name" => "User",
"cpf" => "36156291830",
"email" => "user#email.net",
)
end
end
end
this is my .rspec
--color
--format documentation
--require rails_helper
this is my rails_helper
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'shoulda-matchers'
require 'rack/test'
require 'faker'
require 'rake'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.include Shoulda::Matchers::ActiveModel, type: :model
config.include Shoulda::Matchers::ActiveRecord, type: :model
config.fixture_path = "#{::Rails.root}/spec/factories"
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
config.use_transactional_fixtures = false
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
am'i forgetting something?
You do not have namespaces for UsersController
try:
module Api
module VersionOne
class UsersController < ApiController
# your code
end
end
end
You may also need to change ApiController to Api::VersionOne::ApiController

Trouble including controller macros for rspec tests /w devise

I am getting a uninitialized constant ControllerMacros (NameError), perhaps similar to these issues (1, 2, 3). I must be screwing up the syntax while trying to include controller macros so I can login with devise and pass controller tests in rspec. Link to GitHub repo.
Rails 4.1.8 and Ruby 2.1.2
spec/controllers/static_pages_controller_spec.rb
require 'rails_helper'
describe StaticPagesController, :type => :controller do
describe "GET #index" do
it "responds successfully with an HTTP 200 status code" do
login_user
get :index
expect(response).to be_success
expect(response).to have_http_status(200)
end
it "renders the index template" do
login_user
get :root
expect(response).to render_template("index")
end
end
end
spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
admin = FactoryGirl.create(:admin)
sign_in :user, admin # sign_in(scope, resource)
end
end
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
added lines to spec/rails_helper
#helps avoid authentication error during rspec:
config.include Devise::TestHelpers, :type => :controller
config.include ControllerMacros, :type => :controller
This worked for me.
spec/support/devise.rb
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Also make sure this line is uncommented in rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
I looks like you may need to add require 'support/controller_macros' to the top of your rails_helper.rb file. This directory would not be included by default with RSpec.

Resources