如何测试是否对从 Rails 和 RSpec 中的数据库中提取的特定对象调用了方法?
How do I test if a method is called on particular objects pulled from the DB in Rails with RSpec?
如果我有一个包含方法 dangerous_action
的 User
模型,并且在某处我有代码在数据库中的特定用户子集上调用该方法,如下所示:
class UserDanger
def perform_dangerous_action
User.where.not(name: "Fred").each(&:dangerous_action)
end
end
如何使用 RSpec 测试它是否在正确的用户上调用该方法,而不实际调用该方法?
我试过这个:
it "does the dangerous thing, but not on Fred" do
allow_any_instance_of(User).to receive(:dangerous_action).and_return(nil)
u1 = FactoryBot.create(:user, name: "Jill")
u2 = FactoryBot.create(:user, name: "Fred")
UserDanger.perform_dangerous_action
expect(u1).to have_recieved(:dangerous_action)
expect(u2).not_to have_recieved(:dangerous_action)
end
但是,当然,错误是 User 对象没有响应 has_recieved?
因为它不是 double 因为它是从数据库中提取的对象。
我想我可以通过猴子修补 dangerous_action
方法并使其写入全局变量来完成这项工作,然后在测试结束时检查全局变量的值,但我认为那将是一种非常丑陋的方式。有没有更好的方法?
我意识到我真的在尝试测试 perform_dangerous_action
方法的两个方面。第一个是数据库提取的范围,第二个是它对出现的用户对象调用正确的方法。
为了测试数据库获取的范围,我真的应该在 User
class:
中创建一个范围
scope :not_fred, -> { where.not(name: "Fred") }
可以通过单独的测试轻松测试。
那么perform_dangerous_action
方法就变成了
def perform_dangerous_action
User.not_fred.each(&:dangerous_action)
end
检查它为 not_fred
用户调用正确方法的测试是
it "does the dangerous thing" do
user_double = instance_double(User)
expect(user_double).to receive(:dangerous_action)
allow(User).to receive(:not_fred).and_return([user_double])
UserDanger.perform_dangerous_action
end
我认为,在许多情况下,您不想将 where
或 where.not
分成一个范围,在这种情况下,您可以存根 ActiveRecord::Relation 本身,例如如:
# default call_original for all normal `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).and_call_original
# stub special `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).with(name: "...")
.and_return(user_double)
在你的情况下,where.not
实际上是调用 ActiveRecord::QueryMethods::WhereChain#not
方法,所以我可以做
allow_any_instance_of(ActiveRecord::QueryMethods::WhereChain)
.to receive(:not).with(name: "Fred")
.and_return(user_double)
如果我有一个包含方法 dangerous_action
的 User
模型,并且在某处我有代码在数据库中的特定用户子集上调用该方法,如下所示:
class UserDanger
def perform_dangerous_action
User.where.not(name: "Fred").each(&:dangerous_action)
end
end
如何使用 RSpec 测试它是否在正确的用户上调用该方法,而不实际调用该方法?
我试过这个:
it "does the dangerous thing, but not on Fred" do
allow_any_instance_of(User).to receive(:dangerous_action).and_return(nil)
u1 = FactoryBot.create(:user, name: "Jill")
u2 = FactoryBot.create(:user, name: "Fred")
UserDanger.perform_dangerous_action
expect(u1).to have_recieved(:dangerous_action)
expect(u2).not_to have_recieved(:dangerous_action)
end
但是,当然,错误是 User 对象没有响应 has_recieved?
因为它不是 double 因为它是从数据库中提取的对象。
我想我可以通过猴子修补 dangerous_action
方法并使其写入全局变量来完成这项工作,然后在测试结束时检查全局变量的值,但我认为那将是一种非常丑陋的方式。有没有更好的方法?
我意识到我真的在尝试测试 perform_dangerous_action
方法的两个方面。第一个是数据库提取的范围,第二个是它对出现的用户对象调用正确的方法。
为了测试数据库获取的范围,我真的应该在 User
class:
scope :not_fred, -> { where.not(name: "Fred") }
可以通过单独的测试轻松测试。
那么perform_dangerous_action
方法就变成了
def perform_dangerous_action
User.not_fred.each(&:dangerous_action)
end
检查它为 not_fred
用户调用正确方法的测试是
it "does the dangerous thing" do
user_double = instance_double(User)
expect(user_double).to receive(:dangerous_action)
allow(User).to receive(:not_fred).and_return([user_double])
UserDanger.perform_dangerous_action
end
我认为,在许多情况下,您不想将 where
或 where.not
分成一个范围,在这种情况下,您可以存根 ActiveRecord::Relation 本身,例如如:
# default call_original for all normal `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).and_call_original
# stub special `where`
allow_any_instance_of(ActiveRecord::Relation)
.to receive(:where).with(name: "...")
.and_return(user_double)
在你的情况下,where.not
实际上是调用 ActiveRecord::QueryMethods::WhereChain#not
方法,所以我可以做
allow_any_instance_of(ActiveRecord::QueryMethods::WhereChain)
.to receive(:not).with(name: "Fred")
.and_return(user_double)