Rspec 模拟和存根与 expect 混淆

Rspec mocks and stubs confuse with expect

我在 rails 上的 rspec 中使用模拟和存根时感到困惑。我有如下测试

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,它失败了,我不知道为什么 expect 是最后一行会失败,我认为 expect 总是在我们 运行 得到结果之前放在最后一行去测试。谁能帮我解释一下?

expect(sth).to receive 设置在调用和测试结束之间要满足的消息期望,并在测试完成后验证该期望。当您将 expect 移动到最后一行时,期望值仅在测试结束时设置,并且没有执行任何代码来满足它,因此它失败了。不幸的是,这意味着破坏准备-执行-测试顺序。

这就是为什么你真的应该很少使用 expect.to receive 并将其替换为 allow.to receiveexpect.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 receivespies。在您的情况下,您可以像这样编写测试:

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 方法,此测试现在将引发异常 class - 保护您免受测试通过,即使您重命名该方法但忘记重命名测试和实施。