Mockito: Essayer d'espionner la méthode appelle la méthode d'origine


351

J'utilise Mockito 1.9.0. Je veux simuler le comportement d'une seule méthode d'une classe dans un test JUnit, j'ai donc

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

Le problème est, dans la deuxième ligne, myClassSpy.method1()est en fait appelé, ce qui entraîne une exception. La seule raison pour laquelle j'utilise des simulateurs est que plus tard, à chaque myClassSpy.method1()appel, la vraie méthode ne sera pas appelée et l' myResultsobjet sera retourné.

MyClassest une interface et myInstanceest une implémentation de cela, si cela importe.

Que dois-je faire pour corriger ce comportement d'espionnage?


Jetez un oeil à ceci: stackoverflow.com/a/29394497/355438
Lu55

Réponses:


610

Permettez-moi de citer la documentation officielle :

Gotcha important sur l'espionnage de vrais objets!

Parfois, il est impossible d'utiliser quand (Object) pour repousser les espions. Exemple:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

Dans votre cas, cela va quelque chose comme:

doReturn(resulstIWant).when(myClassSpy).method1();

27
Que se passe-t-il si j'utilise cette méthode et que ma méthode d'origine est TOUJOURS appelée? Pourrait-il y avoir un problème avec les paramètres que je passe? Voici tout le test: la méthode pastebin.com/ZieY790P send est appelée
Evgeni Petrov

26
@EvgeniPetrov si votre méthode d'origine est toujours appelée, c'est probablement parce que votre méthode d'origine est définitive. Mockito ne se moque pas des méthodes finales et ne peut pas vous avertir de la moquerie des méthodes finales.
MarcG

1
est-ce également possible pour doThrow ()?
Gobliins

1
oui, malheureusement, les méthodes statiques ne sont pas moquables et ne peuvent pas être espionnées. Ce que je fais pour gérer les méthodes statiques, c'est d'enrouler une méthode autour de l'appel statique et d'utiliser doNothing ou doReturn sur cette méthode. Avec des singletons ou des objets scala, je déplace la viande de la logique vers une classe abstraite et cela me donne la possibilité d'avoir une implication de classe de test alternative de l'objet sur laquelle je peux créer un espion.
Andrew Norman

24
Et si la méthode NON finale et NON statique est toujours appelée?
X-HuMan du

27

Mon cas était différent de la réponse acceptée. J'essayais de simuler une méthode de package privé pour une instance qui ne vivait pas dans ce package

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

et les classes de test

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

La compilation est correcte, mais lorsqu'elle essaie de configurer le test, elle appelle à la place la vraie méthode.

Déclarer la méthode protégée ou publique résout le problème, mais ce n'est pas une solution propre.


2
J'ai rencontré un problème similaire, mais le test et la méthode package-private étaient dans le même package. Je pense que Mockito a peut-être des problèmes avec les méthodes de paquet privé en général.
Dave

22

Dans mon cas, en utilisant Mockito 2.0, j'ai dû changer tous les any()paramètres pour nullable()afin de bloquer le véritable appel.


2
Ne vous laissez pas abattre par cette 321 réponse votée, cela a résolu mon problème :) Je me bats avec ça depuis quelques heures!
Chris Kessel

3
C'était la réponse pour moi. Pour le rendre encore plus facile pour ceux qui suivent en se moquant de votre méthode, la syntaxe est la suivante: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder

Avec Mockito 2.23.4, je peux confirmer que ce n'est pas nécessaire, cela fonctionne bien avec anyet les eqapparieurs.
vmaldosan

2
J'ai essayé trois approches différentes sur la version 2.23.4 lib: any (), eq () et nullable (). Seul le dernier a travaillé
ryzhman

Salut, votre solution est vraiment sympa et a aussi fonctionné pour moi. Merci
Dhiren Solanki

16

La réponse de Tomasz Nurkiewicz ne semble pas raconter toute l'histoire!

NB Mockito version: 1.10.19.

Je suis vraiment un newb Mockito, donc je ne peux pas expliquer le comportement suivant: s'il y a un expert qui peut améliorer cette réponse, n'hésitez pas.

La méthode en question ici getContentStringValue, n'est PAS final et NON static .

Cette ligne fait appel à la méthode originale getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

Cette ligne n'appelle pas la méthode d'origine getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

Pour des raisons auxquelles je ne peux pas répondre, l'utilisation isA()entraîne l' doReturnéchec du comportement (?) "Ne pas appeler la méthode" prévu .

Regardons les signatures de méthode impliquées ici: ce sont les deux staticméthodes de Matchers. Le Javadoc dit que les deux sont de retour null, ce qui est un peu difficile à comprendre. Vraisemblablement l' Classobjet passé comme paramètre est examiné mais le résultat n'a jamais été calculé ou rejeté. Étant donné que cela nullpeut représenter n'importe quelle classe et que vous espérez que la méthode simulée ne sera pas appelée, les signatures isA( ... )et le any( ... )retour ne pourraient-ils pas être renvoyés nullplutôt qu'un paramètre générique * <T>?

En tous cas:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

La documentation de l'API ne donne aucun indice à ce sujet. Il semble également que la nécessité d'un tel comportement "ne pas appeler la méthode" soit "très rare". Personnellement, j'utilise cette technique tout le temps : en général, je trouve que la moquerie implique quelques lignes qui "définissent la scène" ... suivies par l'appel d'une méthode qui "reproduit" ensuite la scène dans le contexte simulé que vous avez mis en scène .. .et pendant que vous installez le décor et les accessoires, la dernière chose que vous voulez, c'est que les acteurs entrent à gauche et commencent à jouer leur cœur ...

Mais c'est bien au-delà de ma note salariale ... J'invite des explications de tous les grands prêtres Mockito de passage ...

* "paramètre générique" est-il le bon terme?


Je ne sais pas si cela ajoute de la clarté ou rend le sujet encore plus confus, mais la différence entre isA () et any () est que isA vérifie en fait le type, alors que toute famille de méthodes () a été créée simplement pour éviter la conversion de type du argument.
Kevin Welker

@KevinWelker Merci. Et en effet les noms de méthode ne manquent pas d'une certaine qualité auto-explicative. Cependant, je conteste, et même légèrement, le génie des concepteurs de Mockito pour ne pas avoir documenté adéquatement. Je dois sans aucun doute lire un autre livre sur Mockito. PS en fait il semble y avoir très peu de ressources pour enseigner le "Mockito intermédiaire"!
mike rodent

1
L'histoire est que les méthodes anyXX ont été créées d'abord comme un moyen de traiter uniquement le transtypage. Puis, lorsqu'il a été suggéré d'ajouter la vérification des arguments, ils ne voulaient pas casser les utilisateurs de l'API existante, ils ont donc créé la famille isA (). Sachant que les méthodes any () auraient dû effectuer la vérification de type tout au long, elles ont retardé leur modification jusqu'à ce qu'elles introduisent d'autres changements de rupture dans la révision Mockito 2.X (que je n'ai pas encore essayée). Dans 2.x +, les méthodes anyX () sont des alias pour les méthodes isA ().
Kevin Welker

Je vous remercie. C'est une réponse cruciale pour ceux d'entre nous qui effectuent plusieurs mises à jour de bibliothèque en même temps, car le code qui s'exécutait échoue soudainement et silencieusement.
Dex Stakker

6

Un autre scénario possible qui peut provoquer des problèmes avec les espions est lorsque vous testez des beans de printemps (avec un framework de test de printemps) ou un autre framework qui procède au proxy de vos objets pendant le test .

Exemple

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

Dans le code ci-dessus, Spring et Mockito essaieront de proxy votre objet MonitoringDocumentsRepository, mais Spring sera le premier, ce qui provoquera un appel réel de la méthode findMonitoringDocuments. Si nous déboguons notre code juste après avoir mis un espion sur un objet de dépôt, il ressemblera à ceci dans le débogueur:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean à la rescousse

Si au lieu d' @Autowiredannotation, nous utilisons l' @SpyBeanannotation, nous résoudrons le problème ci-dessus, l'annotation SpyBean injectera également un objet de référentiel mais il sera d'abord mandaté par Mockito et ressemblera à ceci à l'intérieur du débogueur

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

et voici le code:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

1

J'ai trouvé une autre raison pour que l'espion appelle la méthode originale.

Quelqu'un a eu l'idée de se moquer d'une finalclasse et a découvert MockMaker:

Comme cela fonctionne différemment de notre mécanisme actuel et que celui-ci a des limites différentes et que nous voulons recueillir l'expérience et les commentaires des utilisateurs, cette fonctionnalité a dû être explicitement activée pour être disponible; cela peut être fait via le mécanisme d'extension mockito en créant le fichier src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontenant une seule ligne:mock-maker-inline

Source: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Après avoir fusionné et apporté ce fichier sur ma machine, mes tests ont échoué.

J'ai juste dû supprimer la ligne (ou le fichier), et j'ai spy()travaillé.


c'était la raison dans mon cas, j'essayais de me moquer d'une méthode finale mais elle n'arrêtait pas d'appeler la vraie sans un message d'erreur clair qui était déroutant.
Bashar Ali Labadi

1

Un peu tard pour la fête mais les solutions ci-dessus n'ont pas fonctionné pour moi, donc partager mes 0,02 $

Version Mokcito: 1.10.19

MyClass.java

private int handleAction(List<String> argList, String action)

Test.java

MyClass spy = PowerMockito.spy(new MyClass());

Ce qui suit n'a PAS fonctionné pour moi (la méthode actuelle était appelée):

1.

doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());

2.

doReturn(0).when(spy , "handleAction", any(), anyString());

3.

doReturn(0).when(spy , "handleAction", null, null);

SUIVI:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());

0

Une façon de s'assurer qu'une méthode d'une classe n'est pas appelée est de remplacer la méthode par un mannequin.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.