I was trying to test a simple index view, which has following code inside:
- if can? :destroy, MyModel
%th Options
MyModelsController has following options (Inherited Resources + CanCan + Devise):
class MyModelsController < ApplicationController
inherit_resources
nested_belongs_to :mymodel
before_filter :authenticate_user!
load_and_authorize_resource :project
load_and_authorize_resource :mymodel, :through => :project
When running specs, it crashes at the line - if can? :destroy, MyModel
Failure/Error: render
ActionView::Template::Error:
undefined method `authenticate' for nil:NilClass
There's no traceback, nothing to base on...
I thought that maybe I'm not authorized and signed when testing views, but Devise::TestHelpers should only be included in controller tests (and that's how I have it).
I was trying to override method can? in both Ability and the controller, but that gave no effect.
This is described in the CanCan docs for controller testing, and can also be modified to apply to view specs. Here's one way to do it:
require 'spec_helper'
describe "mymodel/index.html.erb" do
before(:each) do
assign(:my_model,mock_model(MyModel))
#ability = Object.new
#ability.extend(CanCan::Ability)
controller.stub(:current_ability) { #ability }
end
context "authorized user" do
it "can see the table header" do
#ability.can :destroy, MyModel
render
rendered.should have_selector('th:contains("Options")')
end
end
context "unauthorized user" do
it "cannot see the table header" do
render
rendered.should_not have_selector('th:contains("Options")')
end
end
end
The 'before :each' code posted by zetetic doesn't work for me. My views bork on the 'can?' method because 'current_ability' in the view returns nil. I fixed it by using this 'before :each' code instead:
#ability = Ability.new(user)
assign(:current_ability, #ability)
controller.stub(:current_user, user)
view.stub(:current_user, user)
The above code simulates a login.
In your spec_helper:
config.include Devise::TestHelpers, :type => :view
In your view spec:
controller.stub!(current_user: [some user])
view.stub!(current_user: [some user])
For new RSpec 3.0 syntax
before(:each) do
assign(:my_model,mock_model(MyModel))
#ability = Object.new.extend(CanCan::Ability)
allow(controller).to receive(:current_ability).and_return(#ability)
end
The problem with the solution from the CanCan wiki is that it requires a #ability. can ... in each example, which doesn't feel very DRY.
Moreover, it doesn't actually stub out the abilities themselves, but the method that returns the controller's ability. The ability is not a stub and consequently the abilities are checked.
If you're using Rspec and want to test just the controller (and not it's abilities), here's how to stub it out:
before(:each) do
ability = mock(:ability).as_null_object
controller.stub(:current_ability).and_return(ability)
end
This works because as_null_object returns truthy values for all methods, so the ability checking methods pass.
Based on John Kloian's example I defined this useful helper:
# spec/support/sign_in.rb
module ViewSpecSignInHelper
def login_as(user)
allow(view).to receive(:signed_in?).and_return true
allow(controller).to receive(:current_user).and_return user
end
end
RSpec.configure do |config|
config.include ViewSpecSignInHelper, type: :view
end
My full spec/support/sign_in.rb looks like this:
module ControllerSpecSignInHelper
def login_as(user)
sign_in(user)
end
end
module FeatureSpecSignInHelper
# See https://github.com/plataformatec/devise/wiki/How-To%3a-Test-with-Capybara
include Warden::Test::Helpers
Warden.test_mode!
# A login_as(user) method is provided already!
end
module ViewSpecSignInHelper
def login_as(user)
allow(view).to receive(:signed_in?).and_return true
allow(controller).to receive(:current_user).and_return user
end
end
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :view
config.include ControllerSpecSignInHelper, type: :controller
config.include FeatureSpecSignInHelper, type: :feature
config.include ViewSpecSignInHelper, type: :view
end
I can now login a user the same way in feature, controller, and view specs:
user = create :user # Using FactoryBot
login_as user
Related
I have a controller action which uses devise's "user_signed_in?" as a condition. The action looks like this:
def show
if user_signed_in?
#do stuff
end
end
But when I am testing this action in RSpec, the tests are failing because the code inside the if block never gets executed. Is there any way to stub the "user_signed_in?" method?
You could mock user_signed_in? method like this:
before do
allow_any_instance_of(Devise::Controllers::Helpers).to receive(:user_signed_in?).and_return(false)
end
The code is not executed because the user is probably not signed in.
To sign in a devise user in rspec, have this in spec/support/controller_macros.rb.
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
user.confirm! #if you are using the "confirmable" module
sign_in user
end
end
end
In spec/rails_helper.rb have this:
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
Then in your controller spec:
describe MyController do
login_user
....
end
I'm using Rails 5, and Devise 3.5.1.
Going through a nice (older) book about creating/testing an API, which uses Devise authentication. It was written before Rails 5, so I chose not to use the new api-only version.
Here's my test...
#/spec/controllers/api/v1/users_controller_spec.rb
require 'rails_helper'
describe Api::V1::UsersController, :type => :controller do
before(:each) { request.headers['Accept'] = "application/vnd.marketplace.v1" }
describe "GET #show" do
before(:each) do
#user = FactoryGirl.create :user
get :show, params: {id: #user.id}, format: :json
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql #user.email
end
it { should respond_with 200 }
end
end
And here's a completely unexpected RSpec error
Devise::MissingWarden:
Devise could not find the `Warden::Proxy` instance on your request environment.
Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack.
If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` module to inject the `request.env['warden']` object for you.
So I go here - http://www.rubydoc.info/gems/devise/Devise/Test/ControllerHelpers
and tried this -> include Devise::Test::ControllerHelpers
which didn't help because the file controller_helpers.rb is nowhere in my project
What did I miss here?
Thanks
You could add the following to your rails_helper:
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
end
This will include Devise::Test::ControllerHelpers module in all :controller specs.
In the spec_helper.rb add:
config.include Devise::Test::ControllerHelpers, :type => :controller
MiniTest, Rails 4
This works for vanilla Rails 4 MiniTest
test\controllers\users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
include Warden::Test::Helpers
include Devise::Test::ControllerHelpers
setup do
# https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
# #user = users(:admin)
# sign_in #user
end
teardown do
Warden.test_reset!
end
test "login as admin" do
#user = users :admin
sign_in #user
get :dashboard
assert_redirected_to admin_dashboard_path
end
end
I would like to add a module that includes a method to help me log in as a user. Here it is:
module TestHelper
require 'spec_helper'
ALL_USERS = ["administrator", "instructor", "regular_user"]
def self.login_as(user_type)
user = User.find_by(global_role_id: GlobalRole.send(user_type))
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
end
end
The spec that's calling it is
require 'spec_helper'
RSpec.describe QuestionsController, :type => :controller do
include Devise::TestHelpers
include TestHelper
describe "a test" do
it "works!" do
TestHelper.login_as("administrator")
end
end
end
And here is the spec_helper
RSpec.configure do |config|
config.include TestHelper, :type => :controller
The error I get is: undefined method 'env' for nil:NilClass It looks like my module doesn't have access to #request.
My question is: how do I access and #request in the external Module?
In addition to the other answers, you should consider only including this module for relevant spec types using the following code:
config.include TestHelper, type: :controller # Or whatever spec type(s) you're using
You could pass #request in to TestHelper.login_as from the tests. Eg
module TestHelper
def self.login_as(user_type, request)
...
request.env['devise.mapping'] = Devise.mappings[:user]
...
end
end
...
describe 'log in' do
it 'works!' do
TestHelper.login_as('administrator', #request)
end
end
But probably better to follow the macro pattern from the devise wiki if other devise mappings in play.
I have a PostDecorator class in app/decorators/post_decorator.rb.
It has a method that calls Devise's current_user method. It looks like this:
class PostDecorator < Draper::Decorator
delegate_all
def voter
h.current_user
end
end
I have a PostDecorator spec in spec/decorators/post_decorator_spec.rb
require 'spec_helper'
describe PostDecorator, type: :decorator do
let(:post) { FactoryGirl.create(:post) }
let(:user) { FactoryGirl.create(:user) }
before { allow(helper).to receive(:current_user) { user } }
describe 'voter' do
it 'returns the current_user' do
expect(post.voter).to eq user
end
end
end
When I run this I get an undefined method error:
<Draper::HelperProxy:0x007fb1c4f85890 ... does not implement: current_user
Gem Versions:
draper (1.4.0)
rspec-rails (3.4.1)
devise (3.5.5)
Also I should note everything in my app/lib directory is auto loaded. In application.rb I have config.autoload_paths << Rails.root.join('lib')
Two things I think you need to do.
1.) Add devise test helpers to your decorator tests,
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :decorator
end
2.) you actually need to sign_in to expect post.voter to eq an actual users
require 'spec_helper'
describe PostDecorator, type: :decorator do
let(:post) { FactoryGirl.create(:post) }
let(:user) { FactoryGirl.create(:user) }
before do
sign_in user
end
describe '.voter' do
it 'returns the current_user' do
expect(post.voter).to eq user
end
end
end
The issue is related to Draper.
Decorator is unable to access helper methods after sending an ActionMailer email.
This is an open issue on Draper's GitHub
To solve this I just modified the User Factory by adding a confirmed_at:
factory :user do
...
confirmed_at DateTime.now
end
This way Devise won't send a Confirmation email.
According to this from the devise wiki I should be able to use a login_user helper method in my controller tests. Accordingly I have the following within the spec directory:
#spec_helper.rb
...
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
...
and
#support/controller_macros.rb
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = Factory.create(:user)
sign_in #user
end
end
end
however calling the helper doesn't work:
#requests/some_spec.rb
require 'spec_helper'
describe "GET /guides/edit" do
login_user
end
Can someone point toward where I'm going wrong. The test suite works about from this. I get a undefined local variable or method message so I guess the module isn't being included.
Rails 3.0.7
rspec 2.6.0
devise 1.3.4
backtrace
I imagine there are a couple of problems with this approach. First is that you're using request specs, not controller specs, so the login_user method is not made available by config.extend ControllerMacros, :type => :controller. Second, even if you are able to include the method it most likely won't work anyway, since the Devise test helpers are specifically written for controller/view tests, not integration tests.
Take a look at David Chelimsky's answer to this SO question, which may be of help.
I can't answer for sure... but the code smell for me is the "before(:each)" defined inside the helper. why don't you try:
#support/controller_macros.rb
module ControllerMacros
def login_user
#request.env["devise.mapping"] = Devise.mappings[:user]
#user = Factory.create(:user)
sign_in #user
end
end
and
#requests/some_spec.rb
require 'spec_helper'
describe "GET /guides/edit" do
before(:each) do
login_user
end
end
and if that fails - maybe it just can't find #request - in which case, pass it as a variable to login_user
Edit:
Looks like you might need to include the devise test helpers.
The rdoc says you should have this file:
# spec/support/devise.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
Not sure if that differs from how you've already got it in spec_helper.rb
... looks pretty similar to me.
I have same issue with Rails 3.0.10 rspec 2.6.0 devise 1.3.4 spork-0.9.0.rc9 on my controller specs, i have changed config. extend to config.include and its work !
Forget to confirm if your app is not confirmable. Your code should look like
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