Simular la primera llamada falla, la segunda llamada se realiza correctamente

119

Quiero usar Mockito para probar el código (simplificado) a continuación. No sé cómo decirle a Mockito que falle la primera vez y luego tenga éxito la segunda.

for(int i = 1; i < 3; i++) {
  String ret = myMock.doTheCall();

  if("Success".equals(ret)) {
    log.write("success");
  } else if ( i < 3 ) {
    log.write("failed, but I'll try again. attempt: " + i);
  } else {
    throw new FailedThreeTimesException();
  }
}

Puedo configurar la prueba de éxito con:

Mockito.when(myMock).doTheCall().thenReturn("Success");

Y la prueba de falla con:

Mockito.when(myMock).doTheCall().thenReturn("you failed");

Pero, ¿cómo puedo probar que si falla una vez (o dos) y luego tiene éxito, está bien?

jb.
fuente

Respuestas:

250

De los documentos :

A veces necesitamos apuntar con diferente valor de retorno / excepción para la misma llamada al método. El caso de uso típico podría ser iteradores burlones. La versión original de Mockito no tenía esta función para promover la burla simple. Por ejemplo, en lugar de iteradores, se podría usar Iterable o simplemente colecciones. Aquellos ofrecen formas naturales de troquelado (por ejemplo, utilizando colecciones reales). Sin embargo, en casos excepcionales, la stubing de llamadas consecutivas podría ser útil:

when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
  .thenReturn("foo");

//First call: throws runtime exception:
mock.someMethod("some arg");

//Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));

Entonces, en tu caso, querrás:

when(myMock.doTheCall())
   .thenReturn("You failed")
   .thenReturn("Success");
Jon Skeet
fuente
25
Esto me señaló en la dirección correcta (gracias) por mi escenario que trataba con métodos vacíos; en ese caso, debe usar el estilo alternativo ...doThrow(new RuntimeException()).doNothing().when(myMock).doTheCall();
haggisandchips
38

La forma más corta de escribir lo que quieres es

when(myMock.doTheCall()).thenReturn("Success", "you failed");

Cuando proporcione varios argumentos thenReturnasí, cada argumento se usará como máximo una vez, excepto el último argumento, que se usa tantas veces como sea necesario. Por ejemplo, en este caso, si realiza la llamada 4 veces, obtendrá "éxito", "falló", "falló", "falló".

Dawood ibn Kareem
fuente
22

Dado que el comentario relacionado con esto es difícil de leer, agregaré una respuesta formateada.

Si está intentando hacer esto con una voidfunción que solo lanza una excepción, seguida de un paso sin comportamiento, entonces haría algo como esto:

Mockito.doThrow(new Exception("MESSAGE"))
            .doNothing()
            .when(mockService).method(eq());
anoneironauta
fuente
4

Para agregar esto y esta respuesta, también puede usar un bucle para encadenar las llamadas simuladas. Esto es útil si necesita simular lo mismo varias veces, o simular algún patrón.

Por ejemplo (aunque sea descabellado):

import org.mockito.stubbing.Stubber;

Stubber stubber = doThrow(new Exception("Exception!"));
for (int i=0; i<10; i++) {
    if (i%2 == 0) {
        stubber.doNothing();
    } else {
        stubber.doThrow(new Exception("Exception"));
    }
}
stubber.when(myMockObject).someMethod(anyString());
typoerrpr
fuente
De hecho, no sabía nada de este Stubber y lo encontré útil, +1.
Mihai Morcov
Esta debería ser la respuesta correcta si necesita llamar a un método void.
db80
4

Tengo una situación diferente, quería simular una voidfunción para la primera llamada y ejecutarla normalmente en la segunda llamada.

Esto funciona para mi:

Mockito.doThrow(new RuntimeException("random runtime exception"))
       .doCallRealMethod()
       .when(spy).someMethod(Mockito.any());
Mohammed Elrashidy
fuente