首页 > 解决方案 > 从间谍调用原始方法

问题描述

我有一个服务对象,看起来像这样:

class Service
  def call
    do_something if anything
  end

  private

  def do_something
    # do something
  end
end

我想测试do_something在某些情况下调用的那个。

我试着写这样的东西:

RSpec.describe Service do
  it 'calls do_something' do
    service = instance_double(Service)
    service.stub(:do_something)
    expect(Service).to receive(:new).and_return service
    Service.new.call

    expect(service).to have_received(:do_something)
  end
end

显然这不起作用,因为我没有callservice. 但是,如果我使用service = instance_double('Service', call: true)它不会调用call,所以do_something永远不会被调用。

allow(service).to receive(:call).and_call_original如果可能的话,我正在寻找类似的东西(也许在这种情况下instance_doubleobject_doublewhich 替换更有意义。有这样的东西吗?

标签: rubyrspec

解决方案


正如您已经尝试并看到的那样,它and_call_original仅适用于部分双打,因此无法按照文档所述尝试将其与其他类型的双打一起使用。

如果我们仔细查看部分双文档,我们可以看到它partial double被描述为:

A partial test double is an extension of a real object in a system that is instrumented with
test-double like behavior in the context of a test. This technique is very common in Ruby
because we often see class objects acting as global namespaces for methods. For example,
in Rails:

person = double("person")
allow(Person).to receive(:find) { person }
In this case we're instrumenting Person to return the person object we've defined whenever
it receives the find message. We can also set a message expectation so that the example
fails if find is not called:

person = double("person")
expect(Person).to receive(:find) { person }
RSpec replaces the method we're stubbing or mocking with its own test-double like method.
At the end of the example, RSpec verifies any message expectations, and then restores the
original methods.

Note: we recommend enabling the verify_partial_doubles config option.

上面他们说:A partial test double is an extension of a real object 这意味着做expect(Service.new).to receive(:call).and_call_original将是有效的,因为我们是在一个最初是创建的真实世界对象的部分双精度上进行的,并且期望对其进行设置,使其成为部分双精度。但是如果我们这样做expect(object_double(Service.new)).to receive(:call).and_call_original将不起作用,因为我们将这个期望设置在一个纯双精度上,它是一个验证双精度,而不是现实世界对象的扩展。这意味着在部分双精度中,我们直接针对真实世界或真实系统对象,但在纯双精度情况下,我们正在与双精度对话。这是基于文档类比,它适用于其他类型的双打,而不仅仅是object_double.

如果您在该方法的文档中查看此链接and_call_original,您会注意到这句话:

Use and_call_original to make a partial double response as it normally would

这是有道理的,因为它会像往常一样做出响应,并且通常在系统中,我们在真实对象或类上调用原始方法,而不是在双精度上。

[注意] 在这里你要显式地测试do_something私有方法。就您而言,您只需要测试公共方法,不应测试私有方法。因为如果使用私有方法的公共方法成功运行,那肯定意味着私有方法正在按预期工作。

所以为了确保你训练你的do_something私有方法,你需要创建两个上下文:

context 'when anything is true' do
  tests for call method goes here in this case
end

context 'when anything is false' do
  tests for call method goes here
end

这意味着anything,例如,如果一个模型确实model.save确保在 save 方法上设置期望,因此在一个上下文中它返回false导致do_method does不会被执行。在其他情况下when true,请确保您设置了一个期望以使其返回true,以便您的do_something方法被调用和执行,这隐含地意味着它得到了训练,如果在这种情况下调用测试通过,那肯定意味着它do_something按预期工作。要了解有关测试私有方法的更多信息,您可以观看 Sandi Matz 的精彩演讲


推荐阅读