web-dev-qa-db-ja.com

RSpecスタブメソッドは異なる値を順番に返すことができますか?

他のオブジェクト、Membersのlocation出力をマージするメソッドlocationを持つモデルファミリがあります。 (メンバーは家族に関連付けられていますが、ここでは重要ではありません。)

たとえば、与えられた

  • member_1にはlocation == 'サンディエゴ(旅行、5月15日を返します)'があります
  • member_2にはlocation == 'San Diego'があります

Family.locationが「San Diego(member_1 travelling、return 15 May)」を返す場合があります。詳細は重要ではありません。

Family.locationのテストを簡単にするために、Member.locationをスタブ化します。ただし、上記の例のように2つの異なる(指定された)値を返す必要があります。理想的には、これらは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が一連の例を実行しているとき、元のクラスは復元されないため、この種の後続の例は "poisons"をオーバーライドします。

それで、スタブ化されたメソッドが各呼び出しで異なる指定された値を返すようにする方法はありますか?

62
Mike Blyth

メソッドをスタブして、呼び出されるたびに異なる値を返すことができます。

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

したがって、最初に@family.locationを呼び出すと「first」が返され、2回目には「second」が返され、それ以降に呼び出すと「other」が返されます。

138
idlefingers

RSpec 3構文:

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

承認されたソリューションは、特定の数の呼び出しがあり、特定のデータシーケンスが必要な場合にのみ使用してください。しかし、行われる呼び出しの数がわからない場合、またはデータの順序を気にしない場合毎回何か違うというだけですか? OPが言ったように:

単にシーケンス内の異なる値を返すだけでもOKです

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
1
thisismydesign

何らかの理由で古い構文を使用したい場合でも、次のことができます。

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

上記のソリューションの概要を試してみましたが、うまくいきません。代替実装でスタブ化することで問題を解決しました。

何かのようなもの:

@family.stub(:location) { Rand.to_s }
0
matteo