Kann die RSpec-Stubbed-Methode nacheinander unterschiedliche Werte zurückgeben?

74

Ich habe eine Modellfamilie mit einer Methode, locationdie die locationAusgaben anderer Objekte, Mitglieder, zusammenführt. (Mitglieder sind mit Familien verbunden, aber das ist hier nicht wichtig.)

Zum Beispiel gegeben

  • member_1 hat location== 'San Diego (Reisen, Rückkehr am 15. Mai)'
  • member_2 hat location== 'San Diego'

Family.location gibt möglicherweise 'San Diego (Mitglied_1 reist, kehrt am 15. Mai zurück)' zurück. Die Einzelheiten sind unwichtig.

Um das Testen von Family.location zu vereinfachen, möchte ich Member.location stubben. Ich brauche es jedoch, um zwei verschiedene (angegebene) Werte wie im obigen Beispiel zurückzugeben. Im Idealfall würden diese auf einem Attribut von basieren member, aber die einfache Rückgabe verschiedener Werte in einer Sequenz wäre in Ordnung. Gibt es eine Möglichkeit, dies in RSpec zu tun?

Es ist möglich, die Member.location-Methode in jedem Testbeispiel zu überschreiben, z

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

aber ich bezweifle, dass dies eine gute Praxis ist. Wenn RSpec eine Reihe von Beispielen ausführt, wird in keinem Fall die ursprüngliche Klasse wiederhergestellt, sodass diese Art der Überschreibung nachfolgende Beispiele "vergiftet".

Gibt es also eine Möglichkeit, dass eine Stubbed-Methode bei jedem Aufruf unterschiedliche, angegebene Werte zurückgibt?

Mike Blyth
quelle

Antworten:

162

Sie können eine Methode stubben, um bei jedem Aufruf unterschiedliche Werte zurückzugeben.

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

Wenn Sie @family.locationes also zum ersten Mal aufrufen , wird "first" zurückgegeben, das zweite Mal "second" und bei allen nachfolgenden Aufrufen wird "other" zurückgegeben.

Leerlauffinger
quelle
Danke @idlefingers! Was ist, wenn Sie eine große Anzahl von Werten zurückgeben möchten?
La-comadreja
4
@ La-comadreja sagen, Sie haben eine lange Reihe von Zeichenfolgen aufgerufen my_big_array, die Sie tun könnten allow(@family).to receive(:location).and_return(*my_big_array). Hoffe das hilft.
Leerlauffinger
3
Wie wäre es, wenn Sie beim ersten Aufruf einen Fehler auslösen und beim zweiten Mal einen Wert zurückgeben?
James Klein
@ JamesKlein Ich versuche das gleiche zu tun. Hast du es jemals herausgefunden?
Theblang
Ich habe den hier erwähnten Vorschlag verwendet , der im Wesentlichen darin besteht, einen Block und einen Zähler zu verwenden, die Sie selbst erhöhen.
Theblang
17

RSpec 3-Syntax:

allow(@family).to receive(:location).and_return("abcdefg", "bcdefgh")
nichts Besonderes hier
quelle
6

Die akzeptierte Lösung sollte nur verwendet werden, wenn Sie eine bestimmte Anzahl von Anrufen haben und eine bestimmte Datenfolge benötigen. Aber was ist, wenn Sie nicht wissen, wie viele Anrufe getätigt werden, oder sich nicht um die Reihenfolge der Daten kümmern, nur dass es jedes Mal etwas anderes ist? Wie OP sagte:

Es wäre in Ordnung, einfach verschiedene Werte in einer Sequenz zurückzugeben

Das Problem dabei and_returnist, dass der Rückgabewert gespeichert wird. Das heißt, selbst wenn Sie etwas Dynamisches zurückgeben, erhalten Sie immer das Gleiche.

Z.B

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

Ein praktisches Beispiel wäre die Verwendung von Fabriken und das Erhalten derselben IDs:

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

In diesen Fällen können Sie den Methodenkörper stubben, damit der Code jedes Mal ausgeführt wird:

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

Beachten Sie, dass Sie selfin diesem Kontext keinen Zugriff auf das Member-Objekt über haben, sondern Variablen aus dem Testkontext verwenden können.

Z.B

member = build(:member)
allow(member).to receive(:location) do
  { residence: Faker::Address.city, work: member.male? 'his_work' : 'her_work' }
end
Das ist mein Design
quelle
Wenn Sie allow with receive mehrmals aufrufen müssen, übergeben Sie einen Block, anstatt "and_return ()" zu verwenden
Rodrigo Dias
1

Wenn Sie aus irgendeinem Grund die alte Syntax verwenden möchten, können Sie dennoch:

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

Ich habe die Lösungsübersicht hier oben ausprobiert, aber sie funktioniert bei mir nicht. Ich habe das Problem durch Stubbing mit einer Ersatzimplementierung gelöst.

Etwas wie:

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