Ruby on rails RSpec:对于在我调用的函数中实例化的对象,我怎么能不使用'allow_any_instance_of'?

Ruby on rails RSpec:对于在我调用的函数中实例化的对象,我怎么能不使用'allow_any_instance_of'?,ruby-on-rails,unit-testing,rspec,rspec-rails,rspec3,Ruby On Rails,Unit Testing,Rspec,Rspec Rails,Rspec3,我有一个类a,它有一个方法M,我想为它编写一个测试T。问题是方法M创建了一个新对象O。我想模拟这个新对象的方法F A类 def M(p1、p2) @o=o.新(p1,p2) 结束 结束 O类 def F(q) ... 结束 结束 我可以很容易地使用RSpec的allow\u any\u instance\u功能来实现这一点,但是我真的看不到使用allow或expect来实现这一点的方法。我知道我可以模拟一个现有实例和一个类的方法,但是从我的测试中,我无法使它对在我测试的方法中创建的对象的方法起

我有一个类
a
,它有一个方法
M
,我想为它编写一个测试
T
。问题是方法
M
创建了一个新对象
O
。我想模拟这个新对象的方法
F

A类
def M(p1、p2)
@o=o.新(p1,p2)
结束
结束
O类
def F(q)
...
结束
结束
我可以很容易地使用RSpec的
allow\u any\u instance\u功能来实现这一点,但是我真的看不到使用
allow
expect
来实现这一点的方法。我知道我可以模拟一个现有实例和一个类的方法,但是从我的测试中,我无法使它对在我测试的方法中创建的对象的方法起作用

T:processdo
它“有效”吗
#这很有效
允许(O.)的任何实例接收(:F.)和返回(123)
...
结束
它“不起作用”吗
#这失败了
允许(O).接收(:F.)和_返回(123)
...
结束
结束
我如何知道它失败了?

我用一个
put()
更改了我的
F
方法,当我使用
allow(O)
时,我可以在屏幕上看到输出。当我使用
allow\u any\u instance\u of()时,它根本不会出现。
。所以我知道只有在后一种情况下,它才能像预期的那样工作

  def F(q)
    puts("If I see this, then F() was not mocked properly.")
    ...
  end
我认为,
allow(O)…
应该连接到该类,因此每当创建新实例时,模拟函数都会跟随,但显然不是这样

您是否有RSpec测试以不同的方式处理此类模拟案例,而不涉及使用
allow\u any\u instance\u of()
函数

我问这个问题的原因是因为它是从RSpec3.3开始的(
@allow old syntax
),所以听起来我们不应该再使用这个功能了,特别是一旦RSpec4.x问世,它可能就会消失。

这是为什么

allow(O).to receive(:F).and_return(123)
不起作用的原因是
:F
不是
O
的方法,因此
O
从不接收此消息(方法调用)

对您来说,最好的解决方案是重构代码以使用依赖项注入。(请注意,您的示例非常抽象,如果您提供了一个更接近实际的示例,则可能会进行更好的重构)

通过依赖项注入,您将不需要决定实例化哪个类。这将使代码解耦(A停止与O的耦合,现在它只依赖于它所使用的O接口),并使测试变得更容易

(*)有人可能会说,
allow_any_实例
更容易(参与更少,键入更少),但它已经做到了,如果可能的话应该避免

这是什么原因

allow(O).to receive(:F).and_return(123)
不起作用的原因是
:F
不是
O
的方法,因此
O
从不接收此消息(方法调用)

对您来说,最好的解决方案是重构代码以使用依赖项注入。(请注意,您的示例非常抽象,如果您提供了一个更接近实际的示例,则可能会进行更好的重构)

通过依赖项注入,您将不需要决定实例化哪个类。这将使代码解耦(A停止与O的耦合,现在它只依赖于它所使用的O接口),并使测试变得更容易

(*)有人可能会说,
allow_any_实例
更容易(参与更少,键入更少),但它已经做到了,如果可能的话应该避免

(作为一个小插曲:我理解可能需要对代码进行彻底的匿名化,但您仍然可以遵循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 
我个人更喜欢这种方法,而不是像前面建议的那样创建模拟类。 这可能是众所周知的,但为了清楚起见:mock类imho的问题是您不再测试class
a
,而是测试mock。在某些情况下,这可能是有用的,但从最初的问题来看,不清楚它是否适用于本案,也不清楚这是否是不必要的复杂。其次:如果您的代码如此复杂(例如,某个方法创建一个新对象,然后调用
F
),我宁愿1)重构代码使其可测试,和/或2)测试副作用(例如
F
添加审计日志行,设置状态,…)。我不需要“测试”我的实现(调用的是正确的方法),但它是否执行了(当然,一如既往,也有例外情况,例如调用外部服务或其他东西时——但同样,所有这些都是无法从原始问题中推断出来的)。

(作为一个小插曲:我理解可能需要对代码进行彻底的匿名化,但您仍然可以遵循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