Usando Mockito con múltiples llamadas al mismo método con los mismos argumentos


289

¿Hay alguna manera de que un método tropezado devuelva diferentes objetos en invocaciones posteriores? Me gustaría hacer esto para probar respuestas no determinadas de un ExecutorCompletionService. es decir, para probar que, independientemente del orden de devolución de los métodos, el resultado permanece constante.

El código que estoy buscando probar se parece a esto.

// Create an completion service so we can group these tasks together
ExecutorCompletionService<T> completionService =
        new ExecutorCompletionService<T>(service);

// Add all these tasks to the completion service
for (Callable<T> t : ts)
    completionService.submit(request);

// As an when each call finished, add it to the response set.
for (int i = 0; i < calls.size(); i ++) {
    try {
        T t = completionService.take().get();
        // do some stuff that I want to test
    } catch (...) { }        
}

Respuestas:


254

Puede hacerlo usando el thenAnswermétodo (cuando se encadena con when):

when(someMock.someMethod()).thenAnswer(new Answer() {
    private int count = 0;

    public Object answer(InvocationOnMock invocation) {
        if (count++ == 1)
            return 1;

        return 2;
    }
});

O utilizando el doAnswermétodo estático equivalente :

doAnswer(new Answer() {
    private int count = 0;

    public Object answer(InvocationOnMock invocation) {
        if (count++ == 1)
            return 1;

        return 2;
    }
}).when(someMock).someMethod();

634

Qué tal si

when( method-call ).thenReturn( value1, value2, value3 );

Puede poner tantos argumentos como desee entre corchetes de thenReturn, siempre que sean del tipo correcto. El primer valor se devolverá la primera vez que se llame al método, luego la segunda respuesta, y así sucesivamente. El último valor se devolverá repetidamente una vez que todos los demás valores se hayan agotado.


44
Esto funcionará con un simulacro, pero no con un espía. Si necesita evitar llamar al método original, necesita doAnswer (...). When (someSpy) .someMethod (...).
Yuri

66
@Yuri - no del todo. No necesitas doAnswero escribir un Answeren el caso que mencionas. Solo puedes usar doReturn(...).when(someSpy).someMethod(...). Parece razonable suponer que Emma está interesada en simulacros, en lugar de espías, pero creo que podría agregar algo a mi respuesta para explicar esto. Gracias por el comentario.
Dawood ibn Kareem

@DawoodibnKareem permite decir que para la primera llamada quiero devolver un valor y para la segunda llamada quiero lanzar una excepción. ¿Cómo se puede hacer esto?
Rito

@Rito Lea la respuesta de Volodymyr o la respuesta de Raystorm. Ambos cubren ese caso.
Dawood ibn Kareem

Una respuesta tan gloriosa.
wild_nothing

151

Como se señaló anteriormente, casi todas las llamadas son encadenables.

Entonces puedes llamar

when(mock.method()).thenReturn(foo).thenReturn(bar).thenThrow(new Exception("test"));

//OR if you're mocking a void method and/or using spy instead of mock

doReturn(foo).doReturn(bar).doThrow(new Exception("Test").when(mock).method();

Más información en la Documentación de Mockito .


3
¡Muy útil! ¿Qué pasaría la cuarta vez que mock.methodse llamó en este ejemplo? Quiero algo así, volver a la primera vez pero volver a la barra para TODO el resto.
javaPlease42

66
Cada invocación adicional en el simulacro devolverá el último 'thenReturn' o el último 'thenThrow' Muy útil
Francois Lacoursiere

Gracias por las excelentes y sencillas instrucciones. Nunca supe esto hasta ahora. Estaba luchando por encontrar cómo recuperar dos resultados diferentes en dos llamadas idénticas. Ahórrame toneladas de tiempo.
CharlesC

68

Incluso puedes encadenar doReturn()invocaciones de métodos como esta

doReturn(null).doReturn(anotherInstance).when(mock).method();

lindo no es :)


4

Implementé una MultipleAnswerclase que me ayuda a encontrar respuestas diferentes en cada llamada. Aquí el fragmento de código:

private final class MultipleAnswer<T> implements Answer<T> {

    private final ArrayList<Answer<T>> mAnswers;

    MultipleAnswer(Answer<T>... answer) {
        mAnswers = new ArrayList<>();
        mAnswers.addAll(Arrays.asList(answer));
    }

    @Override
    public T answer(InvocationOnMock invocation) throws Throwable {
        return mAnswers.remove(0).answer(invocation);
    }
}

1
¿Puedes inicializar ese objeto de una manera corta, simple y legible?
usr-local-ΕΨΗΕΛΩΝ

1

Los siguientes pueden usarse como un método común para devolver diferentes argumentos en diferentes llamadas a métodos. Lo único que debemos hacer es pasar una matriz con el orden en el que se deben recuperar los objetos en cada llamada.

@SafeVarargs
public static <Mock> Answer<Mock> getAnswerForSubsequentCalls(final Mock... mockArr) {
    return new Answer<Mock>() {
       private int count=0, size=mockArr.length;
       public Mock answer(InvocationOnMock invocation) throws throwable {
           Mock mock = null;
           for(; count<size && mock==null; count++){
                mock = mockArr[count];
           }

           return mock;    
       } 
    }
}

Ex. getAnswerForSubsequentCalls(mock1, mock3, mock2);devolverá el objeto mock1 en la primera llamada, el objeto mock3 en la segunda llamada y el objeto mock2 en la tercera llamada. Debería usarse así. when(something()).doAnswer(getAnswerForSubsequentCalls(mock1, mock3, mock2)); Esto es casi similar awhen(something()).thenReturn(mock1, mock3, mock2);


1

En relación con la respuesta de @ [Igor Nikolaev] de hace 8 años, el uso de un Answerpuede simplificarse de alguna manera usando una expresión lambda disponible en Java 8.

when(someMock.someMethod()).thenAnswer(invocation -> {
    doStuff();
    return;
});

o más simplemente:

when(someMock.someMethod()).thenAnswer(invocation -> doStuff());

1

Estilo BDD:

import static org.mockito.BDDMockito.*;
...
    @Test
    void submit() {
        given(yourMock.yourMethod()).willReturn(1, 2, 3);

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.