Rspec 模拟和存根与期望混淆
Posted
技术标签:
【中文标题】Rspec 模拟和存根与期望混淆【英文标题】:Rspec mocks and stubs confuse with expect 【发布时间】:2021-12-31 10:36:34 【问题描述】:在 rspec on rails 中使用模拟和存根时我感到困惑。我有如下测试
require 'rails_helper'
class Payment
attr_accessor :total_cents
def initialize(payment_gateway, logger)
@payment_gateway = payment_gateway
@logger = logger
end
def save
response = @payment_gateway.charge(total_cents)
@logger.record_payment(response[:payment_id])
end
end
class PaymentGateway
def charge(total_cents)
puts "THIS HITS THE PRODUCTION API AND ALTERS PRODUCTION DATA. THAT'S BAD!"
payment_id: rand(1000)
end
end
class LoggerA
def record_payment(payment_id)
puts "Payment id: #payment_id"
end
end
describe Payment do
it 'records the payment' do
payment_gateway = double()
allow(payment_gateway).to receive(:charge).and_return(payment_id: 1234)
logger = double('LoggerA')
expect(logger).to receive(:record_payment).with(1234)
payment = Payment.new(payment_gateway, logger)
payment.total_cents = 1800
payment.save
end
end
好的,当我运行 rspec 时它可以工作,没问题,但是当我尝试将 expect
移动到最后一行时,如下所示:
payment = Payment.new(payment_gateway, logger)
payment.total_cents = 1800
payment.save
expect(logger).to receive(:record_payment).with(1234)
我尝试运行 rpsec,但它失败了,我不知道为什么期望是最后一行会失败,我认为期望总是在我们运行某些东西以获得结果进行测试之前放在最后一行。谁能给我解释一下?
【问题讨论】:
【参考方案1】:expect(sth).to receive
设置在调用和测试结束之间要满足的消息期望,并在测试完成后验证该期望。当您将expect
移动到最后一行时,期望在测试结束时设置,并且没有执行任何代码来满足它,因此它失败了。不幸的是,这意味着打破了准备-执行-测试的顺序。
这就是为什么你真的应该很少使用 expect.to receive
并将其替换为 allow.to receive
和 expect.to have_received
# prepare
allow(logger).to receive(:record_payment)
# execute
..
# test
expect(logger).to have_received(:record_payment).with(1234)
allow.to receive
设置了一个模拟代理,它开始跟踪收到的消息,然后可以通过expect.to have_received
显式验证。一些对象会自动设置它们的模拟代理,例如你不需要allow.to receive
用于具有预定义响应的双打或spies
。在您的情况下,您可以编写如下测试:
payment_gateway = double
allow(payment_gateway).to receive(:charge).and_return(payment_id: 1234)
logger = double('LoggerA', record_payment: nil)
payment = Payment.new(payment_gateway, logger)
payment.total_cents = 1800
payment.save
expect(logger).to have_received(:record_payment).with(1234)
其他说明
我强烈建议使用verifiable_doubles,它可以保护您免受误报:
payment_gateway = instance_double(PaymentGateway)
allow(payment_gateway).to receive(:charge).and_return(payment_id: 1234)
如果 PaymentGateway 类上没有定义 charge
方法,此测试现在将引发异常 - 即使您重命名该方法但忘记在测试和实现中重命名它,也可以保护您免受测试通过。
【讨论】:
谢谢你的解释,我明白了。以上是关于Rspec 模拟和存根与期望混淆的主要内容,如果未能解决你的问题,请参考以下文章