web-dev-qa-db-fra.com

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

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

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

Le problème est que, dans la deuxième ligne, myClassSpy.method1() est en train d'être appelé, ce qui entraîne une exception. La seule raison pour laquelle j'utilise des simulacres est que, plus tard, à chaque appel de myClassSpy.method1(), la méthode réelle ne sera pas appelée et l'objet myResults sera renvoyé. 

MyClass est une interface et myInstance est une implémentation de cela, si cela compte.

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

256
Dave

Permettez-moi de citer la documentation officielle :

Important getcha sur l'espionnage d'objets réels!

Parfois, il est impossible d'utiliser (Object) pour écraser des 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 ressemble à quelque chose comme:

doReturn(resulstIWant).when(myClassSpy).method1();
461
Tomasz Nurkiewicz

Mon cas était différent de la réponse acceptée. J'essayais de me moquer d'une méthode package-private pour une instance qui ne résidait 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 tente de configurer le test, elle appelle la méthode réelle.

Déclarer la méthode protected ou public corrige le problème, même si ce n'est pas une solution propre.

24
Maragues

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

NB version Mockito: 1.10.19.

Je suis vraiment un mockito newb, je ne peux donc pas expliquer le comportement suivant: s'il existe un expert capable d'améliorer cette réponse, n'hésitez pas.

La méthode en question ici, getContentStringValue, estPASfinal etPASstatic.

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

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

Cette ligne n'appelle pas appelle 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 de isA() entraîne l'échec du comportement souhaité de la méthode "Ne pas appeler de méthode" de doReturn.

Regardons les signatures de méthodes impliquées ici: ce sont toutes deux des méthodes static de Matchers. La Javadoc dit que tous les deux retournent null, ce qui est un peu difficile à comprendre. Vraisemblablement, l'objet Class passé en tant que paramètre est examiné, mais le résultat n'est jamais calculé, ni ignoré. Étant donné que null peut représenter n'importe quelle classe et que vous espérez que la méthode fausse ne sera pas appelée, les signatures de isA( ... ) et any( ... ) ne peuvent-elles que renvoyer null au lieu d'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 aucune idée à ce sujet. Il semble également dire que la nécessité d'un tel comportement de "méthode de non-appel" est "très rare". Personnellement, j'utilise cette technique tout le temps : en général, je trouve que se moquer implique quelques lignes qui "définissent la scène" ... suivi de l'appel d'une méthode qui "reproduit" ensuite la scène dans le contexte factice ont mis en scène ... et pendant que vous montez le décor et les accessoires, la dernière chose que vous voulez, c'est que les acteurs entrent sur scène et commencent à jouer leur coeur ...

Mais c’est bien au-delà de mon niveau de salaire… J'invite les explications de tous les grands prêtres Mockito qui passent….

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

14
mike rodent

Dans mon cas, avec Mockito 2.0, je devais changer tous les paramètres any () en nullable () afin de remplacer le véritable appel.

12
ejaenv

Un autre scénario possible pouvant causer des problèmes avec les espions est le cas où vous testez haricots printaniers (avec framework de test printanier) ou un autre framework permettant de représenter 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 d’alimenter votre objet MonitoringDocumentsRepository par proxy, mais Spring sera le premier, ce qui provoquera un véritable appel de la méthode findMonitoringDocuments. Si nous déboguons notre code juste après avoir mis un espion sur un objet de référentiel, il ressemblera à ceci dans le débogueur:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean à la rescousse

Si à la place l'annotation @Autowired est utilisée l'annotation @SpyBean, nous résoudrons le problème ci-dessus, l'annotation SpyBean injectera également un objet de référentiel, mais ce sera d'abord traité 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é encore une autre raison pour que Spy appelle la méthode d'origine.

Quelqu'un a eu l'idée de se moquer d'une classe final et a trouvé environ MockMaker:

Comme cela fonctionne différemment de notre mécanisme actuel et que celui-ci a différentes limites, et comme nous souhaitons recueillir l'expérience et les commentaires des utilisateurs, cette fonctionnalité devait ê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.MockMaker contenant 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é.

Il me suffisait de supprimer la ligne (ou le fichier) et spy() fonctionnait.

0
Matruskan

Réponse pour les utilisateurs de scala: Même mettre doReturn en premier ne fonctionne pas! Voir ce post .

0
Nick Resnick

Une façon de s’assurer qu’une méthode d’une classe n’est pas appelée consiste à 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");
            };
        });
0
Geoffrey Ritchey