104 votes

Est-ce que la méthode stubbed de RSpec peut renvoyer des valeurs différentes dans une séquence ?

Je dispose d'un modèle Family avec une méthode location qui fusionne les sorties de location d'autres objets, les 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 (en voyage, retour le 15 mai)'
  • member_2 a location == 'San Diego'

Family.location pourrait retourner 'San Diego (membre_1 en voyage, retour le 15 mai)' Les détails ne sont pas importants.

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

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

it "lorsque la résidence est la même" do 
  class Member
    def location
      return {:residence=>'Domicile', :travail=>'son_travail'} if self.male?
      return {:residence=>'Domicile', :travail=>'son_travail'}
    end
  end
  @family.location[:residence].should == 'Domicile'
end

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

Alors, y a-t-il un moyen d'avoir une méthode simulée renvoyant des valeurs différentes et spécifiées à chaque appel?

219voto

idlefingers Points 15957

Vous pouvez simuler une méthode pour retourner des valeurs différentes à chaque fois qu'elle est appelée;

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

Ainsi, la première fois que vous appelez @family.location, elle renverra 'first', la deuxième fois elle renverra 'second', et toutes les fois suivantes que vous l'appelez, elle renverra 'other'.

21voto

Syntaxe RSpec 3:

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

14voto

thisismydesign Points 751

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

retourner simplement des valeurs différentes dans une séquence serait acceptable

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

Par exemple :

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

Ou un exemple pratique serait d'utiliser des factories et d'obtenir les mêmes IDs :

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 simuler le corps de la méthode pour exécuter le code à chaque fois :

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

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

Par exemple :

membre = build(:membre)
allow(membre).to receive(:location) do
  { résidence: Faker::Address.city, travail: membre.male? 'son_travail' : 'son_travail' }
end

1voto

ndn Points 15391

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

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

0voto

matteo Points 706

J'ai essayé la solution expliquée ci-dessus mais cela ne fonctionne pas pour moi. J'ai résolu le problème en utilisant un substitut d'implémentation.

Quelque chose comme:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X