如何避免使用 expect_any_instance_of 锤子?
How to avoid using the expect_any_instance_of hammer?
我有几个这样的测试:
it 'should invite user again' do
admin_user = create(:invited_admin_user)
expect_any_instance_of(AdminUser).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
我很想这样写:
it 'should invite user again' do
admin_user = create(:invited_admin_user)
expect(admin_user).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
但是如果我这样做,测试就会失败。知道为什么会这样吗?我正在使用 factory_bot 创建 AdminUser 实例。
我试过在测试中放入 puts 语句和 invite 方法来确认 ID。
def invite!(_param1 = AdminUser.new, _param2 = {})
puts 'ID in invite!' + self.id.inspect
super(_param1, _param2)
end
it 'should invite user again' do
admin_user = create(:invited_admin_user)
puts 'adminuser created' + admin_user.id.inspect
expect(admin_user).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
结果
adminuser created7768
ID in invite!7768
问题是您的代码正在重新查找记录并将其实例化为一个新对象,因此接收消息的对象与您测试中的对象不同。
要解决这个问题,请执行以下操作:
it 'should invite user again' do
admin_user = create(:invited_admin_user)
expect(User).to receive(:find).with(admin_user.id).and_return(admin_user)
expect(admin_user).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
这只会拦截 User.find 调用和 returns 您的测试对象,而不是它通常会初始化的对象。
你为什么首先要在这里使用模拟?
it 'should invite user again' do
admin_user = create(:invited_admin_user)
patch :reinvite, params: { id: admin_user.to_param }
expect(admin_user.reload.invited).to eq(true)
end
如果要避免对数据库的冗余调用,整个测试应该写成a) 没有真正的数据库对象创建(FactoryGirl#build
,) b) 没有patch
调用(直接调用相应控制器的方法,) 和 c) 模拟在两者之间调用的所有内容。
NB 我个人认为没有任何理由在所有内容都被模拟的情况下进行测试:它们与代码本身几乎没有区别。我的意思是,我们可能会在测试和代码中犯错误,检查 patch
调用相应控制器的方法是愚蠢的:它已经在 Rails 测试中检查过了。我总是尝试在适用时测试 真实 事物(例如用户确实已更改,而不是调用了某些方法。)
我有几个这样的测试:
it 'should invite user again' do
admin_user = create(:invited_admin_user)
expect_any_instance_of(AdminUser).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
我很想这样写:
it 'should invite user again' do
admin_user = create(:invited_admin_user)
expect(admin_user).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
但是如果我这样做,测试就会失败。知道为什么会这样吗?我正在使用 factory_bot 创建 AdminUser 实例。
我试过在测试中放入 puts 语句和 invite 方法来确认 ID。
def invite!(_param1 = AdminUser.new, _param2 = {})
puts 'ID in invite!' + self.id.inspect
super(_param1, _param2)
end
it 'should invite user again' do
admin_user = create(:invited_admin_user)
puts 'adminuser created' + admin_user.id.inspect
expect(admin_user).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
结果
adminuser created7768
ID in invite!7768
问题是您的代码正在重新查找记录并将其实例化为一个新对象,因此接收消息的对象与您测试中的对象不同。
要解决这个问题,请执行以下操作:
it 'should invite user again' do
admin_user = create(:invited_admin_user)
expect(User).to receive(:find).with(admin_user.id).and_return(admin_user)
expect(admin_user).to receive(:invite!).and_return(true)
patch :reinvite, params: { id: admin_user.to_param }
end
这只会拦截 User.find 调用和 returns 您的测试对象,而不是它通常会初始化的对象。
你为什么首先要在这里使用模拟?
it 'should invite user again' do
admin_user = create(:invited_admin_user)
patch :reinvite, params: { id: admin_user.to_param }
expect(admin_user.reload.invited).to eq(true)
end
如果要避免对数据库的冗余调用,整个测试应该写成a) 没有真正的数据库对象创建(FactoryGirl#build
,) b) 没有patch
调用(直接调用相应控制器的方法,) 和 c) 模拟在两者之间调用的所有内容。
NB 我个人认为没有任何理由在所有内容都被模拟的情况下进行测试:它们与代码本身几乎没有区别。我的意思是,我们可能会在测试和代码中犯错误,检查 patch
调用相应控制器的方法是愚蠢的:它已经在 Rails 测试中检查过了。我总是尝试在适用时测试 真实 事物(例如用户确实已更改,而不是调用了某些方法。)