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 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 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 模拟和存根与期望混淆的主要内容,如果未能解决你的问题,请参考以下文章

Rspec期望调用分配(=)方法

RSpec 存根方法可以按顺序返回不同的值吗?

rspec 3 - 存根类方法

Android混淆总结篇

无法使用 rspec 存根辅助方法

有没有办法用 Rspec 存根包含模块的方法?