对Rspec的`should_receive`有一个不那么具有侵入性的替代方案吗?

时间:2012-08-28 12:46:16

标签: ruby rspec

在编写Rspec测试时,我经常对should_receive感到沮丧。我想知道是否存在较少侵入性的替代方案。

例如:

describe "making a cake" do
  it "should use some other methods" do
    @baker.should_receive(:make_batter)
    @baker.make_cake
  end
end

should_receive的调用是一个很好的描述,但它会破坏我的代码,因为should_receive通过屏蔽原始方法来工作,make_cake无法继续,除非make_batter实际上返回了一些击球手。所以我把它改成了这个:

@baker.should_receive(:make_batter).and_return(@batter)

这很难看,因为:

  • 看起来就像我正在测试make_batter正确返回@batter,但我实际上强制伪造版make_batter 1}}返回。
  • 它迫使我单独设置@batter
  • 如果make_batter有任何重要的副作用(这可能是代码味道,我想)我也必须让这些发生。

我希望should_receive(:make_batter)验证方法调用并将其传递给原始方法。如果我想将其行为存根以进行更好的隔离测试,我会明确地这样做:@baker.stub(:make_batter).and_return(@batter)

有没有办法在不阻止原始方法调用的情况下执行should_receive之类的操作?我的问题是不良设计的症状吗?

3 个答案:

答案 0 :(得分:62)

看起来委托Myron Marston提到的原始方法的更好的API实际上已添加到rspec-mocks v2.12.0

所以现在你可以在任何时候“想要设置消息预期而不干扰对象如何响应消息”时执行此操作“:

@baker.should_receive(:make_batter).and_call_original

感谢您添加此内容,Myron。

答案 1 :(得分:11)

您可以should_receive运行原始方法,如下所示:

@baker.should_receive(:make_batter, &@baker.method(:make_batter))

should_receivestub都支持传递一个块实现(调用方法时eval'd)。 &@baker.method(:make_batter)获取原始方法对象的句柄,并将其作为块实现传递。

FWIW,我们希望提供一个更好的API来委托原始方法(参见this issue),但是在不破坏向后兼容性的情况下添加该功能很困难。

答案 2 :(得分:6)

您遇到should_receive的问题,因为您正在测试make_cake方法的实施细节。编写测试时,您应该只关注行为而不是内部方法调用序列。否则,稍后重构代码也会导致对所有测试进行大量重构。

当你想要孤立地测试你的类时,模拟和存根会派上用场。编写单元测试时,应将测试对象与任何其他对象隔离。当您同时处理多个对象时,两者都可以作为替身。在这种情况下,您可以使用should_receive来确保测试主题通过调用方法正确地将某些任务委托给另一个对象。