使用RSpec测试使用CanCan和Devise的视图

22
我试图测试一个简单的索引视图,其中包含以下代码:
- if can? :destroy, MyModel
  %th Options

MyModelsController有以下选项(继承资源+ 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

运行测试时,它在这行代码 - if can? :destroy, MyModel 处崩溃。

Failure/Error: render
   ActionView::Template::Error:
      undefined method `authenticate' for nil:NilClass

没有追踪记录,没有依据可以参考...

我以为可能是在测试视图时没有得到授权和签名,但 Devise::TestHelpers 应该仅包含在控制器测试中(我是这样做的)。

我试图同时覆盖 Ability 和控制器中的 can? 方法,但没有效果。

6个回答

30

这在CanCan控制器测试文档中有描述,也可以修改以适用于视图规范。以下是一种实现方式:

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

我不知道我怎么错过了它,但它完美地工作了,谢谢! - farnoy
我还必须在controller上以及view.current_ability上进行存根,但这很完美。 - Gareth

6

zetetic发布的“before :each”代码对我无效。我的视图在“can?”方法上出错,因为视图中的“current_ability”返回nil。我使用以下“before :each”代码进行修复:

@ability = Ability.new(user)
assign(:current_ability, @ability)
controller.stub(:current_user, user)
view.stub(:current_user, user)

以上代码模拟了一个登录过程。


6

在你的spec_helper文件中:

config.include Devise::TestHelpers, :type => :view

在你的视图规范中:
controller.stub!(current_user: [some user])
view.stub!(current_user: [some user])

成功了!我之前尝试过在控制器中包含Devise :: TestHelpers,:type =>:controller,但仍然有问题。将helpers包含到视图中解决了问题。 - ajahongir
非常好用。谢谢! - Joshua Muheim

2

针对 RSpec 3.0 的新语法

  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

1
CanCan wiki提供的解决方案存在问题,每个示例都需要@ability.can ...,这不太符合DRY原则。
此外,它并没有真正地存根化能力本身,而是存根了返回控制器能力的方法。能力不是一个存根,因此会检查能力。
如果您正在使用Rspec,并且只想测试控制器(而不是它的能力),以下是如何将其存根化:
before(:each) do
  ability = mock(:ability).as_null_object
  controller.stub(:current_ability).and_return(ability)
end

这是因为as_null_object返回所有方法的真值,所以能力检查方法通过。

0

基于John Kloian的示例,我定义了这个有用的辅助工具:

# 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

我的完整spec/support/sign_in.rb看起来像这样:

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

现在,我可以在功能测试、控制器测试和视图测试中以同样的方式登录用户:

user = create :user # Using FactoryBot
login_as user

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接