Testing views that use CanCan and Devise with RSpec
Asked Answered
S

6

22

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.

Sheldonshelduck answered 16/2, 2011 at 15:28 Comment(0)
M
30

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
Multiplicand answered 16/2, 2011 at 21:35 Comment(2)
I don't know how I missed it, but it works perfectly, thanks!Sheldonshelduck
I also had to stub view.current_ability as well as on controller but this was perfectMama
R
6

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.

Reliquary answered 14/1, 2012 at 12:38 Comment(0)
T
6

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])
Travelled answered 3/5, 2013 at 15:44 Comment(2)
worked! I had tried to include Devise::TestHelpers, :type => :controller before BUT still had a probled. including helpers to view fixed )Marisamariscal
Works very well. Thank you!Intro
R
2

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
Repugnance answered 4/1, 2014 at 10:14 Comment(0)
D
1

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.

Dixie answered 9/6, 2013 at 11:2 Comment(0)
I
0

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
Intro answered 11/9, 2019 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.