Rails控制器的一个动作创建一个辅助类的实例(比如SomeService
),它执行一些工作并返回结果,类似于:
def create
...
result = SomeService.new.process
...
end
我想要保留SomeService#process
返回的内容。
我的问题是 - 我该怎么做?
以下作品:
allow_any_instance_of(SomeService).to receive(:process).and_return('what I want')
但是,rspec-mock
文档不鼓励allow_any_instance_of
使用reasons states here:
rspec-mocks API是为单个对象实例设计的,但此功能适用于整个对象类。结果,存在一些语义上令人困惑的边缘情况。例如,在expect_any_instance_of(Widget).to receive(:name).twice中,它不清楚每个特定实例是否应该接收两次名称,或者是否预期两个接收总数。 (它是前者。)
使用此功能通常是一种设计气味。可能是您的测试试图做得太多或者被测对象太复杂了。
这是rspec-mocks最复杂的功能,并且历史上收到的bug报告最多。 (没有一个核心团队积极使用它,这没有帮助。)
我认为这个想法是做这样的事情:
some_service = instance_double('SomeService')
allow(some_service).to receive(:process).and_return('what I want')
但是,如何让控制器使用double而不是创建新实例
是SomeService
?
答案 0 :(得分:2)
我通常会这样做。
let(:fake_service) { your double here or whatever }
before do
allow(SomeService).to receive(:new).and_return(fake_service)
# this might not be needed, depending on how you defined your `fake_service`
allow(fake_service).to receive(:process).and_return(fake_results)
end
答案 1 :(得分:0)
我的建议是重塑与服务对象交互的方式:
class SomeService
def self.call(*args)
new(*args).tap(&:process)
end
def initialize(*args)
# do stuff here
end
def process
# do stuff here
end
def success?
# optional method, might make sense according to your use case
end
end
由于这是一个项目范围的约定,因此我们知道每个.call
都会返回服务对象实例,我们将查询诸如#success?
,#error_messages
等(主要取决于您的用例)。
在测试此类客户端时,我们仅应验证它们是否使用正确的参数调用类方法.call
,就像模拟返回的值一样简单。
对此类方法的单元测试应证明它:
-用适当的参数调用.new
;
-在创建的实例上调用#process
;
-返回创建的实例(不是过程的结果)。
将类方法作为服务对象接口的主要入口点有助于提高灵活性。 #initialize
和#process
都可以设为私有,但我不希望出于测试目的。