web-dev-qa-db-fra.com

La méthode stubbed RSpec peut-elle renvoyer différentes valeurs en séquence?

J'ai un modèle Family avec une méthode location qui fusionne les sorties location d'autres objets, Membres. (Les membres sont associés aux familles, mais ce n'est pas important ici.) 

Par exemple, étant donné

  • member_1 a location == 'San Diego (voyage, retour le 15 mai)'
  • member_2 a location == 'San Diego'

Family.location pourrait revenir 'San Diego (membre_1 en déplacement, retour le 15 mai)' Les détails sont sans importance.

Pour simplifier le test de Family.location, je souhaite stub Member.location. Cependant, j'ai besoin de renvoyer deux valeurs différentes (spécifiées) comme dans l'exemple ci-dessus. Idéalement, ils seraient basés sur un attribut de member, mais renvoyer simplement des valeurs différentes dans une séquence serait OK. Y a-t-il un moyen de faire cela dans RSpec?

Il est possible de remplacer la méthode Member.location dans chaque exemple de test, telle que

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

mais je doute que ce soit une bonne pratique. Quoi qu'il en soit, lorsque RSpec exécute une série d'exemples, il ne restaure pas la classe d'origine. Ce type de substitution "empoisonne" les exemples suivants.

Alors, y a-t-il moyen de faire en sorte qu'une méthode stubbed retourne différentes valeurs spécifiées à chaque appel?

58
Mike Blyth

Vous pouvez utiliser une méthode pour renvoyer des valeurs différentes à chaque appel.

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

Ainsi, la première fois que vous appelez @family.location, il renverra «premier», la deuxième fois, il «renverra», et toutes les fois que vous l'appelerez, il renverra «autre».

126
idlefingers

RSpec 3 syntaxe:

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

Si, pour une raison quelconque, vous souhaitez utiliser l'ancienne syntaxe, vous pouvez toujours:

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

La solution acceptée ne doit être utilisée que si vous avez un nombre d'appels spécifique et avez besoin d'une séquence de données spécifique. Mais que se passe-t-il si vous ne connaissez pas le nombre d'appels qui seront faits, ou si vous vous souciez de l'ordre des données seulement que c'est quelque chose de différent à chaque fois? Comme OP l'a dit:

renvoyer simplement différentes valeurs dans une séquence serait OK

Le problème avec and_return est que la valeur de retour est mémoisée. Cela signifie que même si vous retournez quelque chose de dynamique, vous obtiendrez toujours le même.

Par exemple.

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

Ou un exemple pratique consisterait à utiliser des usines et à obtenir les mêmes identifiants:

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

Dans ces cas, vous pouvez remplacer le corps de la méthode pour que le code soit exécuté à chaque fois:

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

Notez que vous n’avez pas accès à l’objet Membre via self dans ce contexte mais que vous pouvez utiliser des variables du contexte de test.

Par exemple.

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

J'ai essayé la solution ci-dessus mais cela ne fonctionne pas pour moi. J'ai résolu le problème en stubbing avec une implémentation de remplacement.

Quelque chose comme:

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