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

Nat*_*ong 42 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
Run Code Online (Sandbox Code Playgroud)

调用should_receive是一个很好的描述,但它会破坏我的代码,因为should_receive通过屏蔽原始方法来工作,并且make_cake除非make_batter实际返回一些电池,否则无法继续.所以我改成它:

@baker.should_receive(:make_batter).and_return(@batter)
Run Code Online (Sandbox Code Playgroud)

这很难看,因为:

  • 看起来像我的测试是make_batter正确返回@batter,但我居然强迫的仿版make_batter,以返回.
  • 它迫使我分开设置 @batter
  • 如果make_batter有任何重要的副作用(这可能是代码味道,我想)我也必须让这些发生.

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

有没有办法做一些像should_receive没有阻止原始方法调用?我的问题是设计糟糕的症状吗?

Tyl*_*ick 62

它似乎是委托Myron Marston提到的原始方法的更好的API实际上已添加到rspec-mocks v2.12.0中

因此,现在只要您"想要设置消息预期而不干扰对象如何响应消息",您就可以执行此操作":

@baker.should_receive(:make_batter).and_call_original
Run Code Online (Sandbox Code Playgroud)

感谢您添加此内容,Myron.

  • 谢谢*你*用更新的答案重新审视这个问题. (2认同)

Myr*_*ton 11

您可以should_receive像这样运行原始方法:

@baker.should_receive(:make_batter, &@baker.method(:make_batter))
Run Code Online (Sandbox Code Playgroud)

二者should_receivestub支持通过一个块实现(即当该方法被称为eval'd).&@baker.method(:make_batter)获取原始方法对象的句柄,并将其作为块实现传递.

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


小智 6

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

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