首页 > 解决方案 > 用 rspec 观察 `allow` 和 `have_received` 的奇怪行为

问题描述

我在使用allowhave_received在我的规格中遇到问题

我有一个名为的模型Obj,它belongs_to与一个名为Parent. 该Parent模型与 有has_many关系Obj

Obj模型中,我定义了一个名为child_method. 在Parent模型中,我定义了一个名为 的方法calls_child_method,它遍历Obj与之关联的每个并让它们调用child_method

我正在编写一个规范来测试这种行为,如下所示,但它一直失败:

describe 'parent calls_child_method' do
  let(:obj) { Obj.create }

  before do
    allow(obj).to receive(:child_method)
  end

  it 'should call child_method' do
    obj.parent.calls_child_method
    expect(obj).to have_received(:child_method)
  end
end

输出:

expected: 1 time with any arguments
received: 0 times with any arguments

allow_any_instance_of然而,当我用来做间谍/存根时,这似乎通过了:

describe 'parent calls_child_method' do
  let(:obj) { Obj.create }

  before do
    allow(obj).to receive(:child_method)
  end

  it 'should call child_method' do
    expect_any_instance_of(Obj).to receive(:child_method)
    obj.parent.calls_child_method
  end
end

或者如果我直接调用子方法:

describe 'parent calls_child_method' do
  let(:obj) { Obj.create }

  before do
    allow(obj).to receive(:child_method)
  end

  it 'should call child_method' do
    obj.child_method
    expect(obj).to have_received(:child_method)
  end
end

在所有这一切中,我已经验证了Obj创建的实例实际上是child_method通过byebug调试来调用的,以查看它是否被调用。

有人可以帮我理解为什么规范/间谍会这样吗?

标签: ruby-on-railsrspecrspec-railsrspec3

解决方案


这个问题困扰了我很多年。这就是我理解正在发生的事情的方式。

在您失败的情况下obj,是对您新创建的Obj.create. 通过调用expect(obj).to have_received(:child_method)它预计obj应该收到EXACT 引用child_method

obj的父级接收calls_child_method到它时,它会遍历与其关联的每个 Obj。很可能它parent.objs会触发新的数据库调用。在您的情况下,它将找到您的obj,但会对它有不同的引用。而那个不同的参考将是得到的child_method

这就是为什么从技术上讲,您的对象确实收到了方法调用,但通过不同的引用。结果你的期望失败了:(

expect_any_instance_of(Obj)解决问题。但是,通常最好避免它。

您的最后一个示例是成功的,因为您的期望和方法调用使用相同的对象/引用。


推荐阅读