rspec控制器测试错误nil的未定义方法:NilClass

时间:2014-06-02 18:54:17

标签: ruby-on-rails ruby ruby-on-rails-4 rspec bdd

我是Rspec的新手,我正试图进入整个BDD思维模式,所以我对此错误感到非常难过。我有我试图测试的rails引擎。这是公告控制器。基本上在我想要填充课程列表的任何动作之前。

class BulletinsController < ApplicationController
  before_filter :get_courses

  def new
    @bulletin = Bulletin.new(author_id: @user.id)
  end

 ...

 private
 def get_courses
   if @user.has_role? :admin
     @course_list = Course.all.sort_by(&:start_date)
   ...
   end
 end

应用程序控制器有一些我希望在每个请求上运行的方法。我在主机应用程序中使用设计,因此我可以访问current_user方法

class ApplicationController < ::ApplicationController
  before_filter :get_user
  ...
  def get_user
    @user = current_user
  end
  ...
end

这是我想要运行的规范:

describe BulletinsController do
  routes { MyEngine::Engine.routes }
  before { controller.stub(:authenticate_user!).and_return true }
  before { controller.stub(:get_user).and_return (@user = create(:user)) }

  describe "GET #new" do
    it "assigns a new bulletin to @bulletin" do
      bulletin = create(:bulletin)
      controller.stub(:get_courses)
      get :new
      assigns(:bulletin).should eq(bulletin)
    end
  end 
end

当我尝试运行规范时,我收到错误:

NoMethodError: undefined method 'id' for nil:NilClass

我明白我得到的是因为@user在公告大楼中被调用时没有被定义;但是我认为规范中的before块会在删除:get_user过滤器之后定义@user变量。当我在控制台中测试工厂时,一切似乎都是用适当的关联创建的(公告 - &gt;作者,公告 - &gt;课程等)。

我不确定我为什么没有将@user变量传递给我的控制器代码。任何有关rspec的见解和/或好的教程都将不胜感激。

2 个答案:

答案 0 :(得分:3)

除非你了解Devise的工作原理,否则试图将Devise可能使用的方法排除在外会非常困难。

推荐的测试方法是根据文档使用Devise测试助手简单地登录用户: https://github.com/plataformatec/devise#test-helpers

describe BulletinsController do
  routes { MyEngine::Engine.routes }
  before { sign_in(user) }

  let!(:user) { create(:user) }

  describe "GET #new" do
    it "assigns a new bulletin to @bulletin" do
      bulletin = create(:bulletin)
      controller.stub(:get_courses)
      get :new
      assigns(:bulletin).should eq(bulletin)
    end
  end 
end

通过这种方式,您不必关心Devise方法并将其存根。只需专注于测试自己的方法。 :)

答案 1 :(得分:1)

我猜你还需要存根current_user,这就足够了(不需要存根get_user):

before { controller.stub(:current_user).and_return (@user = create(:user)) }

我认为好的做法是让用户(如果你需要不止一次):

routes { MyEngine::Engine.routes }
let!(:user) { create(:user) }
before { controller.stub(:current_user).and_return user }

如果您需要访问私有方法,可以尝试这样的方法:

subject.send(:current_user=, user)

可以是controller而不是subject,不确定支持哪个版本。

更新。实际上,测试私有方法真的很棘手。我检查current_user中的devise定义如下:

def current_#{mapping}
  @current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end

因此,您可以尝试使用存根warden.authenticate来返回user

allow_any_instance_of(Warden).to receive(:authenticate).and_return(create(:user))