RSpec:我如何不能对在我调用的函数中实例化的对象使用`allow_any_instance_of`?

我有一个类A和一个方法M,我想为其编写测试T。问题在于方法M创建了一个新对象O。我想模拟该新对象F的方法O

class A

  def M(p1,p2)
    @o = O.new(p1,p2)
  end

end

class O

  def F(q)
    ...
  end

end

我可以很容易地使用RSpec的allow_any_instance_of功能来做到这一点,但是我真的没有办法仅靠allowexpect来做到这一点。我知道我可以模拟现有实例和类的方法,但是从我的测试中,我无法使其对正在测试的方法中创建的对象的方法起作用。

T :process do
  it "works" do
    # This works
    allow_any_instance_of(O).to receive(:F).and_return(123)
    ...
  end

  it "does not works" do
    # This fails
    allow(O).to receive(:F).and_return(123)
    ...
  end
end

我怎么知道它失败了?

我用F更改了puts()方法,当我使用allow(O)时可以在屏幕上看到该输出。当我使用allow_any_instance_of()时,它根本不会出现。因此,我知道它只能在后者中按预期工作。

  def F(q)
    puts("If I see this,then F() was not mocked properly.")
    ...
  end

我认为allow(O)...应该连接到该类,因此,每当创建一个新实例时,模拟函数就会随之而来,但显然不是。

您是否有RSpec测试以不涉及使用allow_any_instance_of()函数的不同方式来处理这种模拟情况?

我问的原因是因为它是RSpec 3.3以来的marked as obsolete@allow-old-syntax),所以听起来我们不应该再使用此功能,尤其是RSpec 4.x发行后,将消失。

aiaiai5 回答:RSpec:我如何不能对在我调用的函数中实例化的对象使用`allow_any_instance_of`?

原因

allow(O).to receive(:F).and_return(123)

不起作用的是:F不是O的方法,因此O从未收到此消息(方法调用)。

最适合您的解决方案是重构代码以使用依赖项注入。 (请注意,如果您提供了一个真实的示例-更接近地面-可能会有更好的重构,那么您的示例是极端抽象的)

class A
  attr_accessor :o_implementation

  def initialize(o_implementation)
    @o_implementation = o_implementation
  end

  def M(p1,p2)
    @o = o_implementation.new(p1,p2)
  end

end

RSpec.describe A do
  subject { described_class.new(klass) }
  let(:klass) { O }
  let(:a_double) { instance_double(klass) }


  it do 
     allow(klass).to receive(:new).and_return(a_mock)
     allow(a_double).to receive(:F).and_return(123)
  end
end

有了Dependency注入,您就不必再决定要实例化哪个类。这样可以使您的代码解耦(A不再与O耦合,现在仅取决于它所使用的O接口),并使测试变得更容易*。

(*)人们可能会说allow_any_instance更容易(参与程度更少,键入更少),但它有some issues,应尽可能避免。

,

(还有一点:我可以理解可能需要对代码进行非常彻底的匿名化,但是您仍然可以遵循ruby样式指南:方法以小写开头,只有类以大写开头)

因此,首先,allow(O)可以工作,但是只能捕获类方法。如果需要捕获实例方法,则需要为特定实例调用allow

由于您的示例非常稀疏,我看不出为什么不能从测试中拆分对象的创建?如果可能的话,一种非常简单的方法是编写如下内容:

describe :process do 
  before do 
    @o = A.o_maker(p1,p2) 
    allow(@o).to receive(:some_function) { 123 } 
  end 
  it "works" do 
    # do something with `@o` that should call the function
  end
end 

如前所述,我个人更喜欢这种方法而不是创建模拟类。 这可能是众所周知的,但是为了清楚起见:模拟类imho的问题在于您不再测试类A而是模拟了。在某些情况下这可能是有用的,但从原始问题出发,目前尚不清楚它是否适用于这种情况,以及是否不必要地复杂。其次:如果您的代码 如此复杂(例如,创建新对象然后调用F的某种方法),我宁愿1)重构代码以使其可测试,和/或2)测试副作用(例如F添加审核日志行,设置状态,...)。我不需要“测试”我的实现(是否调用了正确的方法),但是它是否执行了(当然,和往常一样,有例外,例如,在调用外部服务或其他东西时,但是所有这些都无法推断出)来自原始问题)。

本文链接:https://www.f2er.com/3075493.html

大家都在问