我正在使用Devise进行身份验证,我正在尝试做的是测试在用户对象上调用方法。所以我的规范看起来像这样:
it "should retrieve something for user" do
@user = Factory.create(:user)
sign_in @user
@user.expects(:something)
get :manage
end
我遇到的问题是除非我这样做,否则会失败:
it "should retrieve something for user" do
@user = Factory.create(:user)
sign_in @user
controller.stubs(:current_user).returns @user
@user.expects(:something)
get :manage
end
如果sign_in @user是设备测试帮助器,那么<p>伪造控制器上的current_user调用似乎很有用。在挖掘调试器之后,当没有伪造出current_user时,@ user实际上正在返回,所以我不确定为什么没有满足期望。
想法?
答案 0 :(得分:3)
当前用户在存储到会话之前被序列化;可能,对于ActiveRecord用户模型,它存储用户的id:
https://github.com/plataformatec/devise/blob/master/lib/devise/test_helpers.rb#L49
这意味着当您的控制器再次抓取用户时:
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb#L47-49
它在会话中找到user_id,并从数据库中重新获取用户。
这意味着控制器中#current_user返回的User对象是与您在测试中传入#sign_in的@user不同的Ruby对象,因此来自一个对象的存根和期望不会附加到另一个对象。 / p>
我没有广泛使用过Devise / Warden,但很确定这是正在发生的事情。您可以尝试在两个实例上打印#object_id以确认这一点。
Picky语义更新2014年2月:
根据您对被测系统(SUT)“边界”的定义 - 即您的控制器,在控制器上对#current_user
进行存储是一种很好的方法。假设#current_user
方法由Devise定义并由Devise混合到您的控制器中,您可以认为#current_user
在您的SUT外部,因此可以公平地进行存根游戏。
您也可以存根Devise正在访问的底层(ActiveRecord),比如通过存根User.find
来返回您的@user
对象。这意味着您的规范正在测试您的操作实现以及#current_user
的Devise实现,因此如果以后更改这些内容,规范将会失败。
让我们说Devise使用User.find(args)
,并说你存根该方法,然后更新版本的Devise改为使用User.where(args).first()
- 你的代码没有改变,但是底层库有,和你的规格失败了。关于这个想法非常普遍地思考,有时你可能会喜欢这种行为(考虑使用例如WebMock来模拟原始HTTP响应而不是存根Net::HTTP
方法,以便以后可以交换http库) ,时间你不会(也许这个设计问题算作一个)。