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

发布于 2024-11-06 07:21:20 字数 927 浏览 7 评论 0原文

我有一个模型系列,其方法 location 合并了其他对象 Members 的 location 输出。 (成员与家庭相关联,但这在这里并不重要。)

例如,给定

  • member_1 具有 location == '圣地亚哥(旅行,5 月 15 日返回)'
  • member_2 具有 location == '圣地亚哥'

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

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

可以在每个测试示例中覆盖 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

但我怀疑这是一个好的做法。无论如何,当 RSpec 运行一系列示例时,它不会恢复原始类,因此这种覆盖会“毒害”后续示例。

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

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

For example, given

  • member_1 has location == 'San Diego (traveling, returns 15 May)'
  • member_2 has location == 'San Diego'

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

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?

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

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

转瞬即逝 2024-11-13 07:21:20

您可以对方法进行存根处理,以在每次调用该方法时返回不同的值;

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

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

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.location it will return 'first', the second time it will return 'second', and all subsequent times you call it, it will return 'other'.

我的鱼塘能养鲲 2024-11-13 07:21:20

RSpec 3 语法:

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

RSpec 3 syntax:

allow(@family).to receive(:location).and_return("abcdefg", "bcdefgh")
左耳近心 2024-11-13 07:21:20

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

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

and_return 的问题是返回值被记忆。这意味着即使您返回动态的东西,您也将始终得到相同的结果。

例如

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

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

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

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

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

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

例如

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

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 data only that it's something different each time? As OP said:

simply returning different values in a sequence would be OK

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

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:

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 self in this context but can use variables from the testing context.

E.g.

member = build(:member)
allow(member).to receive(:location) do
  { residence: Faker::Address.city, work: member.male? 'his_work' : 'her_work' }
end
狼性发作 2024-11-13 07:21:20

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

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

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

@family.stub(:location).and_return('foo', 'bar')
つ低調成傷 2024-11-13 07:21:20

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

像这样的东西:

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

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 }
萌面超妹 2024-11-13 07:21:20

我遇到的问题是,我对一个方法进行了多次调用,但顺序并不总是相同。在这种情况下,我建议使用 .with 来区分实例,并使用方法的参数。

例如,这可能是您的“默认”返回值:

allow(@family).to receive(:location).and_return('her_work')

但是,如果 location 收到像“male”这样的参数,您可以添加:

allow(@family).to receive(:location).with("male").and_return('his_work')

有很多不同的匹配参数类型可以与 .with< 一起使用/code>:

https:// relishapp.com/rspec/rspec-mocks/v/3-2/docs/setting-constraints/matching-arguments

I had the problem that I had multiple calls to a method and not always in the same order. In that case, I'd recommend using .with, to distinguish between instances, using the method's arguments.

So for example, this could be your "default" returning value:

allow(@family).to receive(:location).and_return('her_work')

but then, if location receives an argument like "male", you can add:

allow(@family).to receive(:location).with("male").and_return('his_work')

There are lots of different matching argument types that can be used with .with:

https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/setting-constraints/matching-arguments

悲喜皆因你 2024-11-13 07:21:20

可以帮助您的模式:

allow(MyClass).to receive(:my_method) do |kwargs|
    # define the response for "my_method" (kwargs is the hash of arguments given to the method)
    "my return value"
end

A pattern that can help:

allow(MyClass).to receive(:my_method) do |kwargs|
    # define the response for "my_method" (kwargs is the hash of arguments given to the method)
    "my return value"
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文