web-dev-qa-db-fra.com

Appeler des rappels avec Mockito

J'ai du code

service.doAction(request, Callback<Response> callback);

Comment puis-je utiliser Mockito pour récupérer l'objet de rappel et appeler callback.reply (x)

59
Kurru

Vous voulez configurer un objet Answer qui fait cela. Jetez un œil à la documentation Mockito, sur https://static.javadoc.io/org.mockito/mockito-core/2.8.47/org/mockito/Mockito.html#answer_stubs

Vous pourriez écrire quelque chose comme

when(mockService.doAction(any(Request.class), any(Callback.class))).thenAnswer(
    new Answer<Object>() {
        Object answer(InvocationOnMock invocation) {
            ((Callback<Response>) invocation.getArguments()[1]).reply(x);
            return null;
        }
});

(en remplaçant x par ce qu'il devrait être, bien sûr)

70
Dawood ibn Kareem

Pensez à utiliser un ArgumentCaptor , qui dans tous les cas est une correspondance plus étroite pour "saisir [bing] l'objet de rappel".

/**
 * Captor for Response callbacks. Populated by MockitoAnnotations.initMocks().
 * You can also use ArgumentCaptor.forClass(Callback.class) but you'd have to
 * cast it due to the type parameter.
 */
@Captor ArgumentCaptor<Callback<Response>> callbackCaptor;

@Test public void testDoAction() {
  // Cause service.doAction to be called

  // Now call callback. ArgumentCaptor.capture() works like a matcher.
  verify(service).doAction(eq(request), callbackCaptor.capture());

  assertTrue(/* some assertion about the state before the callback is called */);

  // Once you're satisfied, trigger the reply on callbackCaptor.getValue().
  callbackCaptor.getValue().reply(x);

  assertTrue(/* some assertion about the state after the callback is called */);
}

Bien qu'un Answer soit une bonne idée lorsque le rappel doit retourner immédiatement (lire: de manière synchrone), il introduit également la surcharge de la création d'une classe interne anonyme et de la conversion non sécurisée des éléments à partir de invocation.getArguments()[n] au type de données souhaité. Cela vous oblige également à faire des affirmations sur l'état de pré-rappel du système à partir de WITHIN the Answer, ce qui signifie que votre réponse peut augmenter en taille et en portée.

Au lieu de cela, traitez votre rappel de manière asynchrone: capturez l'objet de rappel transmis à votre service à l'aide d'un ArgumentCaptor. Vous pouvez maintenant faire toutes vos assertions au niveau de la méthode de test et appeler reply quand vous le souhaitez. Cela est particulièrement utile si votre service est responsable de plusieurs rappels simultanés, car vous avez plus de contrôle sur l'ordre de retour des rappels.

48
Jeff Bowman

Si vous avez une méthode comme: -

public void registerListener(final IListener listener) {
    container.registerListener(new IListener() {
        @Override
        public void beforeCompletion() {
        }

        @Override
        public void afterCompletion(boolean succeeded) {
            listener.afterCompletion(succeeded);
        }
    });
}

Ensuite, de la manière suivante, vous pouvez facilement vous moquer de la méthode ci-dessus: -

@Mock private IListener listener;

@Test
public void test_registerListener() {
    target.registerListener(listener);

    ArgumentCaptor<IListener> listenerCaptor =
            ArgumentCaptor.forClass(IListener.class);

    verify(container).registerListener(listenerCaptor.capture());

    listenerCaptor.getValue().afterCompletion(true);

    verify(listener).afterCompletion(true);
}

J'espère que cela pourrait aider quelqu'un, car j'avais passé beaucoup de temps à trouver cette solution

5
Sumit Kumar Saha
when(service.doAction(any(Request.class), any(Callback.class))).thenAnswer(
    new Answer() {
    Object answer(InvocationOnMock invocation) {
        Callback<Response> callback =
                     (Callback<Response>) invocation.getArguments()[1];
        callback.reply(/*response*/);
    }
});
2
Garrett Hall