web-dev-qa-db-fra.com

Est-ce une utilisation appropriée de la méthode de réinitialisation de Mockito?

J'ai une méthode privée dans ma classe de test qui construit un objet Bar couramment utilisé. Le constructeur Bar appelle la méthode someMethod() dans mon objet simulé:

private @Mock Foo mockedObject; // My mocked object
...

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
}

Dans certaines de mes méthodes de test, je veux vérifier que someMethod a également été invoqué par ce test particulier. Quelque chose comme ceci:

@Test
public void someTest() {
  Bar bar = getBar();

  // do some things

  verify(mockedObject).someMethod(); // <--- will fail
}

Cela échoue, car l'objet simulé avait someMethod appelé deux fois. Je ne veux pas que mes méthodes de test se soucient des effets secondaires de ma méthode getBar(), serait-il donc raisonnable de réinitialiser mon objet maquette à la fin de getBar()?

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
  reset(mockedObject); // <-- is this OK?
}

Je demande, car la documentation suggère que la réinitialisation des objets fictifs indique généralement de mauvais tests. Cependant, cela me convient.

Alternative

Le choix alternatif semble appeler:

verify(mockedObject, times(2)).someMethod();

ce qui à mon avis oblige chaque test à connaître les attentes de getBar(), sans gain.

77
Duncan Jones

Je crois que c'est l'un des cas où l'utilisation de reset() est correcte. Le test que vous écrivez teste que "certaines choses" déclenchent un seul appel à someMethod(). L'écriture de l'instruction verify() avec un nombre différent d'appels peut créer de la confusion.

  • atLeastOnce() permet les faux positifs, ce qui est une mauvaise chose car vous voulez que vos tests soient toujours corrects.
  • times(2) empêche le faux positif, mais donne l'impression que vous attendez deux invocations plutôt que de dire "je sais que le constructeur en ajoute une". De plus, si quelque chose change dans le constructeur pour ajouter un appel supplémentaire, le test a maintenant une chance pour un faux positif. Et la suppression de l'appel entraînerait l'échec du test car le test est désormais incorrect au lieu de ce qui est testé est incorrect.

En utilisant reset() dans la méthode d'assistance, vous évitez ces deux problèmes. Cependant, vous devez faire attention à ce qu'il réinitialise également tout stubbing que vous avez fait, alors soyez averti. La principale raison pour laquelle reset() est découragée est d'empêcher

bar = mock(Bar.class);
//do stuff
verify(bar).someMethod();
reset(bar);
//do other stuff
verify(bar).someMethod2();

Ce n'est pas ce que le PO essaie de faire. L'OP, je suppose, a un test qui vérifie l'invocation dans le constructeur. Pour ce test, la réinitialisation permet d'isoler cette seule action et son effet. Ce cas parmi les rares avec reset() peut être utile en tant que. Les autres options qui ne l'utilisent pas ont toutes des inconvénients. Le fait que le PO ait fait ce post montre qu'il réfléchit à la situation et n'utilise pas simplement aveuglément la méthode de réinitialisation.

68
unholysampler

Les utilisateurs de Smart Mockito utilisent à peine la fonction de réinitialisation car ils savent que cela pourrait être le signe de mauvais tests. Normalement, vous n'avez pas besoin de réinitialiser vos simulations, créez simplement de nouvelles simulations pour chaque méthode de test.

Au lieu de reset() veuillez envisager d'écrire des méthodes de test simples, petites et ciblées sur des tests longs et sur-spécifiés. La première odeur potentielle du code est reset() au milieu de la méthode de test.

Extrait de les mockito docs .

Mon conseil est que vous essayez d'éviter d'utiliser reset(). À mon avis, si vous appelez deux fois à someMethod, cela devrait être testé (c'est peut-être un accès à la base de données ou un autre long processus dont vous voulez vous occuper).

Si vous ne vous en souciez vraiment pas, vous pouvez utiliser:

verify(mockedObject, atLeastOnce()).someMethod();

Notez que ce dernier peut provoquer un faux résultat, si vous appelez someMethod à partir de getBar, et non après (c'est un mauvais comportement, mais le test n'échouera pas).

6
greuze

Absolument pas. Comme c'est souvent le cas, la difficulté que vous rencontrez pour rédiger un test propre est un drapeau rouge majeur concernant la conception de votre code de production. Dans ce cas, la meilleure solution est de refactoriser votre code afin que le constructeur de Bar n'appelle aucune méthode.

Les constructeurs doivent construire et non exécuter la logique. Prenez la valeur de retour de la méthode et transmettez-la en tant que paramètre constructeur.

new Bar(mockedObject);

devient:

new Bar(mockedObject.someMethod());

Si cela devait entraîner la duplication de cette logique à de nombreux endroits, envisagez de créer une méthode d'usine qui peut être testée indépendamment de votre objet Bar:

public Bar createBar(MockedObject mockedObject) {
    Object dependency = mockedObject.someMethod();
    // ...more logic that used to be in Bar constructor
    return new Bar(dependency);
}

Si cette refactorisation est trop difficile, l'utilisation de reset () est une bonne solution. Mais soyons clairs - cela indique que votre code est mal conçu.

4
tonicsoft