【问题标题】:False positive with Rspec's "expect to receive"Rspec 的“期望收到”的误报
【发布时间】:2016-11-05 03:21:44
【问题描述】:

我意识到我编写测试的方式会产生误报。

假设我有这个源代码

class MyClass
  def foo
  end
  def bar
    1
  end
end

foo 方法什么都不做,但假设我想编写一个测试,确保它在后台调用bar(即使它没有)。另外,我要保证直接调用bar的结果是1

it "test" do
  inst = MyClass.new
  expect(inst).to receive(:bar).and_call_original
  inst.foo
  expect(inst.bar).to eq(1)
end

所以这是返回 true,但我希望它返回 false。

我想要这条线:

  expect(inst).to receive(:bar).and_call_original

考虑到 在我的测试用例中我直接调用 inst.bar 的事实。我希望它查看foo 方法的内部。

【问题讨论】:

  • 为什么这么复杂? expect(inst.bar).to eq(1) 就够了。
  • 您必须将第二个 expect 移动到另一个测试示例。
  • @AndreyDeineko 这是一个展示概念的例子,而不是我正在使用的代码

标签: ruby-on-rails ruby rspec


【解决方案1】:

您在一个测试用例中定义了 2 个独立的测试用例。您应该将其更改为 2 个单独的测试。

describe '#bar' do
  it "uses #foo" do
    inst = MyClass.new
    allow(inst).to receive(:foo).and_call_original
    inst.bar
    expect(inst).to have_received(:foo)
  end

  it "returns 1" do
    inst = MyClass.new
    # if you don't need to mock it, don't do it
    # allow(inst).to receive(:foo).and_call_original
    expect(inst.bar).to eq(1)
  end

  # if you really, really wan't to do it your way, you can specify the amount of calls
  it "test" do
    inst = MyClass.new
    allow(inst).to receive(:foo).and_call_original
    inst.foo
    expect(inst.bar).to eq(1)
    expect(inst).to have_received(:foo).twice # or replace .twice with .at_least(2).times
  end
end

【讨论】:

  • 是的,这就是我在看到任何答案之前最后所做的事情。感谢您的参与。我会保留一两天,如果没有更好的答案,我会接受您的。
  • allow.to receive 后跟expect.to have_received 有点多余,不是吗?忘记have_received 行,将allow 更改为expect
  • ...除非,我想,你在一个例子的中间,并且想要指定应该接收该方法的 where
  • 没有。这不是多余的。允许准备一个模拟。它可以接听电话,但不能。后来期望我检查它是否确实发生了。这样您就可以准备场景并在最后设置期望调用。您期望的方式分散在测试中。我认为这是在测试结束时期望的更简洁的语法。这样,您必须始终查看规范的末尾以了解测试的内容。
【解决方案2】:

存根通常以两种方式使用:

  1. 检查是否调用了该方法,即expect_any_instance_of(MyClass).to receive(:foo),在这种情况下,它返回的并不是真正重要的
  2. 模拟行为allow_any_instance_of(MyClass).to receive(:method).and_return(fake_response)。这是避免数据库交互或隔离测试中其他依赖项的好方法。

例如,在需要设置 Rails ActiveRecord 模型 Product 的数据的测试中,该模型具有多个关联 comments

let(:product) { Product.new }
let(:comments) { [Comment.new(text: "Foo"), Comment.new(text: "Bar")] }

before :each do
  allow_any_instnace_of(Product).to recieve(:comments).and_return(comments)

现在,当您调用 product.comments 时,在您的任何 it 块中,您将返回一组可在测试中使用的 cmets,而无需靠近您的数据库,这使测试速度提高了几个数量级。

当您使用存根检查方法是否被调用时,关键是在执行调用该方法的操作之前声明期望。例如:

expect_any_instance_of(Foo).to recieve(:bar).exactly(1).times.with('hello')
Foo.new.bar('hello') # will return true

【讨论】:

  • 注意我使用 any_instance_of 是因为我认为在测试中使用它是一个更强大的选项,您不必使用它。这真的取决于情况
猜你喜欢
  • 2017-07-28
  • 2016-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多