2

我最近在 Ruby 2.0、Rails 4 和 Rspec 2.13.1 中发现了这种特殊行为

当我使用 存根实例方法ClassName.any_instance.stub(:method_name)时,它会正确存根和我创建的过去实例。但是,当我通过更改返回值对其进行重新存根时,旧实例返回旧的存根值,而不是新的存根值。

例如,我有这个虚拟类定义

class A
  def test(x)
    return x
  end
end

这个测试描述了行为:

it 'strange stubbing behavior' do
  inst = A.new
  inst.test(1).should eq 1                         #passes
  A.any_instance.stub(:test).and_return(10)
  inst.test(0).should eq 10

  A.any_instance.unstub(:test)                     #has no effect
  A.any_instance.stub(:test).and_return(100)

  inst.test(0).should eq 100                       #expects 100, got 10

  A.any_instance.stub(:test) do |a|
    a + 2
  end
  inst.test(3).should eq 5   #also fails           # also got 10
end

为什么 rspec 会这样?它是定义的行为吗?如果是这样,那么重新创建旧实例的正确方法是什么。或者它是一个错误?

编辑:在其他人给出“质疑问题”的答案之前,我想指出我确实通过重新考虑规范并重新组织它们来解决我原来的问题。但是,我仍然很好奇为什么 RSpec 会这样

4

2 回答 2

2

简而言之,当您在已在该实例上调用存根any_instance.unstub 的情况下使用时,RSpec 似乎确实不会取消存根。类似地,any_instance.stub正如您所发现的,存根无法与以前存根/调用的实例一起使用。

但是,如果您不调用或显式取消存根现有实例,则任何 new stubsany_instance都将在该实例上按预期工作。例如,对您的示例进行以下修改将起作用。

it 'strange stubbing behavior' do
  inst = A.new
  inst.test(1).should eq 1                         #passes
  A.any_instance.stub(:test).and_return(10)
  # SKIP THE INVOCATION SO TEST WILL PASS
  # inst.test(0).should eq 10

  A.any_instance.unstub(:test)                     #has no effect (on existing instances)

  A.any_instance.stub(:test).and_return(100)

  inst.test(0).should eq 100                       #expects 100, got 10

  inst.unstub(:test) # UNSTUB INSTANCE SO TEST WILL PASS                                                            
  A.any_instance.stub(:test) do |a|
    a + 2
  end
  inst.test(3).should eq 5   #also fails           # also got 10
end

不确定这是否是预期的行为,但为此提交了一个问题。RSpec Cucumber 测试,可在https://www.relishapp.com/rspec/rspec-mocks/docs/method-stubs/stub-on-any-instance-of-a-class#any-instance-unstub上查看,仅unstub适用于新实例的测试。

(向@Theresa Luu 致敬,感谢她对此的帮助。)

于 2013-08-07T02:29:09.567 回答
1

In my opinion, the stub method isn't really what you want here.

Generally speaking, if I want to truly stub something, then it should really only need to return the 1 value I want it to. I have never found myself trying to "restub" something as you do above.

That said, there are ways to have your mocks return different values on different calls, which might be all you're trying to do here. In rspec, you can accomplish that by passing multiple values in to your mock call as parameters. For instance:

inst.stub(:test).and_return(1,2,3)
inst.test(0) #=> 1
inst.test(0) #=> 2
inst.test(0) #=> 3
于 2013-08-07T01:52:22.977 回答