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

Posted

技术标签:

【中文标题】RSpec 存根方法可以按顺序返回不同的值吗?【英文标题】:Can RSpec stubbed method return different values in sequence? 【发布时间】:2011-08-22 07:46:43 【问题描述】:

我有一个带有 location 方法的模型 Family,它合并了其他对象成员的 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 运行一系列示例时,它不会恢复原始类,因此这种覆盖“毒害”了后续示例。

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

【问题讨论】:

【参考方案1】:

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

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

所以当你第一次调用@family.location 时,它会返回'first',第二次它会返回'second',之后你调用它,它会返回'other'。

【讨论】:

谢谢@idlefingers!如果你想返回大量的值怎么办? @La-comadreja 说你有一个很长的字符串数组,叫做my_big_array,你可以做allow(@family).to receive(:location).and_return(*my_big_array)。希望这会有所帮助。 第一次调用时抛出错误,第二次返回值呢? @JamesKlein 我正在尝试做同样的事情。你有想过吗? 我使用了here 中提到的建议,它本质上是使用一个块和一个你自己递增的计数器。【参考方案2】:

RSpec 3 语法:

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

【讨论】:

【参考方案3】:

仅当您有特定数量的调用并需要特定的数据序列时,才应使用公认的解决方案。但是如果您不知道将要进行的调用次数,或者不关心数据的顺序,只是每次都不同,该怎么办?正如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' 

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

例如

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

【讨论】:

如果你需要多次调用allow和receive,传递一个block而不是使用"and_return()"【参考方案4】:

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

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

【讨论】:

【参考方案5】:

我已经尝试了上面的解决方案大纲,但它不适用于我。我通过使用替代实现来解决问题。

类似:

@family.stub(:location)  rand.to_s 

【讨论】:

以上是关于RSpec 存根方法可以按顺序返回不同的值吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以在不重置浏览器状态的情况下按顺序运行多个 RSpec/Selenium 测试吗?

您能同时在RSpec中测试状态变化和返回值吗?

rspec 3 - 存根类方法

如果名称按组的顺序不同,R data.table 分组操作返回错误值?

Azure 数据资源管理器获取不同的值并按顺序排列

6.3 遍历字典