我需要测试这样的东西(简化):
class Event
def self.charge_unpaid_events
unpaid_events.each do |event|
event.charge
end
end
...
end
我正在使用FactoryGirl并进行如下测试:
event = FactoryGirl.create(:event)
event.should receive(:charge)
Event.charge_unpaid_events
这失败了。我之前遇到过这个问题,我的理解是在每个循环的上下文中,块参数event
是一个与我的模拟对象event
不同的对象。我在过去通过确保只有一个事件实例并使用Event.any_instance.should receive...
来解决这个问题,但这在我正在尝试测试的上下文中不起作用(出于其他原因我不会去成)。
这种限制是否有更明智的方法?
答案 0 :(得分:1)
很难从这个小代码片段中分辨出来,但我猜测unpaid_events
是scope
或类似的查询。此外,Event
是某种DB支持"传统" Rails-y模型。
根据这些假设并给出了当前编写方法的方式,我通常会说这应该是一个验收规范。有了这种规格,我不会嘲笑任何东西。然而,听起来charge
可能会产生一些影响深远且不良的副作用。
如果确实如此,那么在不更改代码的情况下,您需要存根Event.unpaid_events
来返回一组模拟。通常,对被测对象进行存根是一种代码气味,并指向一个做得太多的对象。
以下是这样的规范可能是什么样的:
it "charges all unpaid events" do
mock_events = [ double(Event, charge: true), double(Event, charge: true) ]
allow(Event).to receive(:unpaid_events).and_return(mock_events)
Event.charge_unpaid_events
mock_events.all?{ |mock| expect(mock).to have_received(:charge) }
end
我可能会做的第一个重构是注入应该收取的事件,默认范围。我可能同时只收取那些无偿的事件:
class Event
def self.charge_unpaid_events(events = unpaid_events)
events.select(&:unpaid?)
.each(&:charge)
end
end
it "charges all the unpaid events" do
paid_events = [ double(Event, unpaid: false), double(Event, unpaid: false) ]
unpaid_events = [
double(Event, unpaid: true, charge: true),
double(Event, unpaid: true, charge: true)
]
mock_events = paid_events + unpaid_events
Event.charge_unpaid_events mock_events
unpaid_events.all?{ |event| expect(event).to have_received(:charge) }
end
it "does not charge paid events" do
# Normally testing 'not' conditions is more work than necessary, however for a
# simple method such as this; plus the fact that charging things that are
# `paid` can have more drastic consequences; I would argue this deserves a spec
paid_events = [ double(Event, unpaid: false), double(Event, unpaid: false) ]
unpaid_events = [
double(Event, unpaid: true, charge: true),
double(Event, unpaid: true, charge: true)
]
mock_events = paid_events + unpaid_events
Event.charge_unpaid_events mock_events
paid_events.all?{ |event| expect(event).not_to have_received(:charge) }
end
此时我会质疑Event
的工作是否负责管理收费以及应收取的费用。我可能会调查将这些内容移到外部服务类中,以便从一个简单的事件对象中收取责任。