Comment se moquer des méthodes void avec Mockito

938

Comment se moquer des méthodes avec un type de retour vide?

J'ai implémenté un modèle d'observateur mais je ne peux pas me moquer de lui avec Mockito parce que je ne sais pas comment.

Et j'ai essayé de trouver un exemple sur Internet mais je n'ai pas réussi.

Ma classe ressemble à ceci:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

Le système n'est pas déclenché avec une simulation.

Je veux montrer l'état du système mentionné ci-dessus. Et faites des affirmations selon eux.

ibrahimyilmaz
la source
6
Attention, les méthodes void sur les mocks ne font rien par défaut!
Ligne
1
@Line, c'est ce que je cherchais. Cela semble évident après l'avoir dit. Mais cela met en évidence un principe moqueur: il suffit de simuler les méthodes des classes simulées pour leurs effets, comme une valeur de retour ou une exception. Je vous remercie!
allenjom

Réponses:

1145

Jetez un œil aux documents de l' API Mockito . Comme le document lié mentionne (point n ° 12) , vous pouvez utiliser l' une des doThrow(), doAnswer(), doNothing(), doReturn()famille de méthodes de cadre Mockito pour se moquer des méthodes vides.

Par exemple,

Mockito.doThrow(new Exception()).when(instance).methodName();

ou si vous souhaitez le combiner avec un comportement de suivi,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

En supposant que vous envisagez de vous moquer du setter setState(String s)dans la classe World ci-dessous, le code utilise la doAnswerméthode pour se moquer de setState.

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());
sateesh
la source
8
@qualidafial: Ouais, je pense que le paramétrage de Void serait mieux car il indique mieux que je ne suis pas intéressé par le type de retour. Je n'étais pas au courant de cette construction, merci de l'avoir signalé.
sateesh
2
doThrow est maintenant # 5 (aussi pour moi qui utilise doThrow, cela a corrigé le message "type 'void' non autorisé ici", pour les followers ...)
rogerdpack
@qualidafial: Je pense que le type de retour de l'appel Answer.answer n'est pas ce qui est renvoyé à la méthode d'origine, c'est ce qui est renvoyé à l'appel doAnswer, probablement si vous voulez faire autre chose avec cette valeur dans votre test.
douze17
1
:( en essayant de se moquer de la version 16.0.1 de RateLimiter.java dans la goyave doNothing (). when (mockLimiterReject) .setRate (100) entraîne l'appel du setRate du RateLimiter entraînant nullpointer puisque mutex est null pour une raison quelconque une fois mockito bytecoded donc il ne s'est pas moqué de ma méthode setRate :( mais l'a appelé à la place :(
Dean Hiller
2
@DeanHiller remarque que setRate()c'est finalet ne peut donc pas être moqué. Essayez plutôt create()une instance qui fait ce dont vous avez besoin. Il ne devrait pas être nécessaire de se moquer RateLimiter.
dimo414
113

Je pense que j'ai trouvé une réponse plus simple à cette question, pour appeler la vraie méthode pour une seule méthode (même si elle a un retour nul), vous pouvez le faire:

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

Ou, vous pouvez appeler la vraie méthode pour toutes les méthodes de cette classe, en procédant comme suit:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);
MarcioB
la source
13
C'est la vraie réponse ici. La méthode spy () fonctionne bien, mais est généralement réservée lorsque vous voulez que l'objet fasse tout normalement.
biggusjimmus
1
Qu'est-ce que ça veut dire? Appelez-vous réellement les méthodes? Je n'ai jamais vraiment utilisé de mockito auparavant.
obesechicken13
Oui, la maquette appellera les vraies méthodes. Si vous utilisez @Mock, vous pouvez spécifier la même chose avec: @Mock (answer = Answers.CALLS_REAL_METHODS) pour obtenir les mêmes résultats.
Ale
70

Ajoutant à ce que @sateesh a dit, lorsque vous souhaitez simplement simuler une méthode void afin d'empêcher le test de l'appeler, vous pouvez utiliser Spycette méthode:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

Lorsque vous souhaitez exécuter votre test, assurez-vous d'appeler la méthode en test sur l' spyobjet et non sur l' worldobjet. Par exemple:

assertEquals(0,spy.methodToTestThatShouldReturnZero());
Omri374
la source
57

La solution du soi-disant problème consiste à utiliser un spy Mockito.spy (...) au lieu d'un mock Mockito.mock (..) .

Spy nous permet de nous moquer partiellement. Mockito est bon dans ce domaine. Parce que vous avez une classe qui n'est pas complète, vous vous moquez ainsi de la place requise dans cette classe.

ibrahimyilmaz
la source
3
Je suis tombé ici parce que j'avais un problème similaire (également, par coïncidence, je testais une interaction sujet / observateur). J'utilise déjà un espion mais je veux que la méthode 'SubjectChanged' fasse quelque chose de différent. Je pourrais utiliser `verify (observer) .subjectChanged (subject) juste pour voir que la méthode a été appelée. Mais, pour une raison quelconque, je préfère de loin remplacer la méthode. Pour cela, une combinaison de l'approche de Sateesh et de votre réponse ici était la voie à suivre ...
gMale
32
Non, cela n'aidera pas vraiment à se moquer des méthodes de nullité. L'astuce consiste à utiliser l'une des quatre méthodes statiques Mockito répertoriées dans la réponse de sateesh.
Dawood ibn Kareem
2
@Gurnard pour votre question jetez un œil à ce stackoverflow.com/questions/1087339/… .
ibrahimyilmaz
Ce dosent fonctionne réellement.
Nilotpal
34

Tout d'abord: vous devez toujours importer mockito static, de cette façon le code sera beaucoup plus lisible (et intuitif):

import static org.mockito.Mockito.*;

Pour des moqueries partielles tout en conservant les fonctionnalités d'origine sur le reste, mockito propose "Spy".

Vous pouvez l'utiliser comme suit:

private World world = spy(World.class);

Pour éliminer l'exécution d'une méthode, vous pouvez utiliser quelque chose comme ceci:

doNothing().when(someObject).someMethod(anyObject());

pour donner un comportement personnalisé à une méthode, utilisez "when" avec un "thenReturn":

doReturn("something").when(this.world).someMethod(anyObject());

Pour plus d'exemples, veuillez trouver les excellents échantillons de mockito dans la doc.

fl0w
la source
4
Qu'est-ce que l'importation statique a à voir avec le fait de le rendre plus lisible?
jsonbourne
2
littéralement rien.
specializt
2
Je pense que c'est une question de goût, j'aime juste que la déclaration ressemble (presque) à une phrase anglaise, et le Class.methodname (). Quelque chose () vs methodname (). Quelque chose est moins lisible.
fl0w
1
Si vous travaillez en équipe, l'importation statique rend la tâche difficile à quiconque de voir d'où vient la méthode, car il y a souvent un caractère générique dans l'importation.
LowKeyEnergy
c'est correct, mais pour le code de test et Mockito, cela devrait être évident et ne pas poser de problème.
fl0w
27

Comment se moquer des méthodes void avec mockito - il y a deux options:

  1. doAnswer - Si nous voulons que notre méthode void moquée fasse quelque chose (moquer le comportement malgré le fait d'être nul).
  2. doThrow- Ensuite, il y a Mockito.doThrow()si vous voulez lever une exception de la méthode void moquée.

Voici un exemple de la façon de l'utiliser (pas un cas d'utilisation idéal, mais je voulais juste illustrer l'utilisation de base).

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("[email protected]")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

}
}

Vous pouvez trouver plus de détails sur la façon de se moquer et de tester les méthodes de vide avec Mockito dans mon article Comment se moquer avec Mockito (Un guide complet avec des exemples)

Dilini Rajapaksha
la source
1
Excellent exemple. Remarque: En java 8, il pourrait être un peu plus agréable d'utiliser un lambda à la place d'une classe annonymous: 'doAnswer ((Answer <Void>) invocation -> {// CODE}). When (mockInstance) .add (method ()); »
miwe
16

Ajout d'une autre réponse au groupe (sans jeu de mots) ...

Vous devez appeler la méthode doAnswer si vous ne pouvez pas \ ne voulez pas utiliser espion. Cependant, vous n'avez pas nécessairement besoin de lancer votre propre réponse . Il existe plusieurs implémentations par défaut. Notamment, CallsRealMethods .

En pratique, cela ressemble à ceci:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

Ou:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));
javamonkey79
la source
16

Dans Java 8, cela peut être rendu un peu plus propre, en supposant que vous ayez une importation statique pour org.mockito.Mockito.doAnswer:

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

Le return null;est important et sans lui, la compilation échouera avec des erreurs assez obscures car elle ne pourra pas trouver un remplacement approprié doAnswer.

Par exemple, un ExecutorServicequi exécute immédiatement tout Runnablepassé execute()peut être implémenté en utilisant:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());
Tim B
la source
2
En une seule ligne: Mockito.doAnswer ((i) -> null) .wh (instance) .method (any ());
Akshay Thorve
@AkshayThorve Cela ne fonctionne pas quand vous voulez vraiment faire des trucs avec i.
Tim B
7

Je pense que vos problèmes sont dus à votre structure de test. J'ai trouvé difficile de mélanger la simulation avec la méthode traditionnelle d'implémentation des interfaces dans la classe de test (comme vous l'avez fait ici).

Si vous implémentez l'écouteur en tant que maquette, vous pouvez alors vérifier l'interaction.

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

Cela devrait vous convaincre que le «monde» fait la bonne chose.

ashley
la source
0

Utiliser Mockito.doThrow comme dans:

Mockito.doThrow(new Exception()).when(instance).methodName();

vous pouvez essayer ce bel exemple:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

Source: http://apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

APISonar
la source