Ruby-on-rails RSpec 存根方法可以按顺序返回不同的值吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5947999/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 01:06:07  来源:igfitidea点击:

Can RSpec stubbed method return different values in sequence?

ruby-on-railstestingrspecstub

提问by Mike Blyth

I have a model Family with a method locationwhich merges the locationoutputs of other objects, Members. (Members are associated with families, but that's not important here.)

我有一个模型家族,location它有一个方法可以合并location其他对象成员的输出。(成员与家庭有关,但这在这里并不重要。)

For example, given

例如,给定

  • member_1 has location== 'San Diego (traveling, returns 15 May)'
  • member_2 has location== 'San Diego'
  • member_1 具有location== '圣地亚哥(旅行,5 月 15 日返回)'
  • member_2 有location== '圣地亚哥'

Family.location might return 'San Diego (member_1 traveling, returns 15 May)' The specifics are unimportant.

Family.location 可能会返回“圣地亚哥(member_1 旅行,5 月 15 日返回)”具体细节并不重要。

To simplify the testing of Family.location, I want to stub Member.location. However, I need it to return two different (specified) values as in the example above. Ideally, these would be based on an attribute of member, but simply returning different values in a sequence would be OK. Is there a way to do this in RSpec?

为了简化 Family.location 的测试,我想存根 Member.location。但是,我需要它返回两个不同的(指定的)值,如上例所示。理想情况下,这些将基于 的属性member,但简单地按序列返回不同的值就可以了。有没有办法在 RSpec 中做到这一点?

It's possible to override the Member.location method within each test example, such as

可以在每个测试示例中覆盖 Member.location 方法,例如

it "when residence is the same" do 
  class Member
    def location
      return {:residence=>'Home', :work=>'his_work'} if self.male?
      return {:residence=>'Home', :work=>'her_work'}
    end
  end
  @family.location[:residence].should == 'Home'
end

but I doubt this is good practice. In any case, when RSpec is running a series of examples it doesn't restore the original class, so this kind of override "poisons" subsequent examples.

但我怀疑这是一种很好的做法。在任何情况下,当 RSpec 运行一系列示例时,它不会恢复原始类,因此这种覆盖“毒药”后续示例。

So, is there a way to have a stubbed method return different, specified values on each call?

那么,有没有办法让存根方法在每次调用时返回不同的指定值?

回答by idlefingers

You can stub a method to return different values each time it's called;

你可以存根一个方法在每次调用时返回不同的值;

allow(@family).to receive(:location).and_return('first', 'second', 'other')

So the first time you call @family.locationit will return 'first', the second time it will return 'second', and all subsequent times you call it, it will return 'other'.

因此,您第一次调用@family.location它时将返回“first”,第二次将返回“second”,随后所有调用它时,它将返回“other”。

回答by nothing-special-here

RSpec 3 syntax:

RSpec 3 语法:

allow(@family).to receive(:location).and_return("abcdefg", "bcdefgh")

回答by thisismydesign

The accepted solution should only be used if you have a specific number of calls and need a specific sequence of data. But what if you don't know the number of calls that will be made, or don't care about the order of dataonly that it's something different each time? As OP said:

仅当您有特定数量的调用并需要特定的数据序列时,才应使用已接受的解决方案。但是,如果您不知道将进行的调用次数,或者不关心数据的顺序只是每次都不同,该怎么办?正如 OP 所说:

simply returning different values in a sequence would be OK

简单地在序列中返回不同的值就可以了

The issue with and_returnis that the return value is memoized. Meaning even if you'd return something dynamic you'll always get the same.

问题and_return在于返回值被记忆。这意味着即使你返回一些动态的东西,你也会得到相同的结果。

E.g.

例如

allow(mock).to receive(:method).and_return(SecureRandom.hex)
mock.method # => 7c01419e102238c6c1bd6cc5a1e25e1b
mock.method # => 7c01419e102238c6c1bd6cc5a1e25e1b

Or a practical example would be using factories and getting the same IDs:

或者一个实际的例子是使用工厂并获得相同的 ID:

allow(Person).to receive(:create).and_return(build_stubbed(:person))
Person.create # => Person(id: 1)
Person.create # => Person(id: 1)

In these cases you can stub the method body to have the code executed every time:

在这些情况下,您可以存根方法体以每次都执行代码:

allow(Member).to receive(:location) do
  { residence: Faker::Address.city }
end
Member.location # => { residence: 'New York' }
Member.location # => { residence: 'Budapest' }

Note that you have no access to the Member object via selfin this context but can use variables from the testing context.

请注意,您无法self在此上下文中访问 Member 对象,但可以使用测试上下文中的变量。

E.g.

例如

member = build(:member)
allow(member).to receive(:location) do
  { residence: Faker::Address.city, work: member.male? 'his_work' : 'her_work' }
end

回答by ndnenkov

If for some reason you want to use the old syntax, you can still:

如果出于某种原因您想使用旧语法,您仍然可以:

@family.stub(:location).and_return('foo', 'bar')

回答by matteo

I've tried the solution outline here above but it does not work for my. I solved the problem by stubbing with a substitute implementation.

我已经尝试了上面的解决方案大纲,但它对我不起作用。我通过使用替代实现进行存根解决了这个问题。

Something like:

就像是:

@family.stub(:location) { rand.to_s }