Cómo verificar múltiples llamadas a métodos con diferentes parámetros

116

Tengo el siguiente método en el que deseo verificar el comportamiento.

public void methodToTest(Exception e, ActionErrors errors) {
    ...

    errors.add("exception.message", 
            ActionMessageFactory.createErrorMessage(e.toString()));

    errors.add("exception.detail",
            ActionMessageFactory.createErrorMessage(e.getStackTrace()[0].toString()));

    ...
}

En mi clase @Test esperaba hacer algo como esto para verificar que errors.add()se llama con "exception.message" y nuevamente con "exception.detail"

verify(errors).add(eq("exception.message"), any(ActionError.class));
verify(errors).add(eq("exception.detail"), any(ActionError.class));

sin embargo Mockito se queja de la siguiente manera

Argument(s) are different! Wanted:
actionErrors.add(
    "exception.message",
    <any>
);

Actual invocation has different arguments:
actionErrors.add(
    "exception.detail",
    org.apache.struts.action.ActionError@38063806
);

¿Cómo puedo decirle a Mockito que verifique ambos valores?

Puntilla
fuente
1
cuando tienes 2 métodos con diferentes firmas, puedes escribir un caso de prueba separado para ambos.
Naveen Babu
8
Sí, pero en este caso es la misma firma de método pero solo valores de argumento diferentes
Brad
podría intentar usarloMockito.reset()
takacsot

Respuestas:

102

Leer más me ha llevado a intentar usar ArgumentCaptors y los siguientes trabajos, aunque mucho más detallados de lo que me gustaría.

ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);

verify(errors, atLeastOnce()).add(argument.capture(), any(ActionMessage.class));

List<String> values = argument.getAllValues();

assertTrue(values.contains("exception.message"));
assertTrue(values.contains("exception.detail"));
Puntilla
fuente
¿Hay alguna manera de asegurarse de que ciertos parámetros se emparejaron utilizando este enfoque? Digamos, por ejemplo, que el método del OP tenía dos argumentos y quería verificar que se llamaron juntos
committedandroider
1
El caso de prueba del OP llama methodToTest()exactamente una vez, por lo tanto, esta respuesta verifica que las dos llamadas se realicen juntas. La captura List<String> valuesque se está afirmando solo contendrá los dos valores que se están probando y ningún otro. También podría agregar assertTrue(values.size == 2). Si esto es lo que quieres, reemplazaría las 3 declaraciones afirmarTrue con un solo Hamcrest ...assertThat(values, contains("exception.message", "exception.detail"));
Brad
¿No llama el caso de prueba de OP a methodToTest () dos veces?
committedandroider
lo siento, no estaba claro. Me refería al escenario en el que OP quería probar que se llamaban dos argumentos en conjunto. Entonces, la firma del método se vería algo así como public void methodToTest (Exception e, Message m, ActionErrors errors) {de modo que se llame a una excepción específica con un mensaje específico. Supuse que podría tener dos ArgumentCaptors y luego recuperar el índice y comparar usando los valores en esos índices en ambas listas de valores
confirmadondroider
El caso de prueba de OP llama methodToTest()una vez. Es el argumento del método que ActionErrors errorsse llama internamente dos veces.
Brad
61

Si el orden de ambas add()llamadas es relevante, puede usar InOrder:

InOrder inOrder = inOrder(errors);
inOrder.verify(errors).add(eq("exception.message"), any(ActionError.class));
inOrder.verify(errors).add(eq("exception.detail"), any(ActionError.class));
Christoph Walesch
fuente
7
Es suficiente pasar un solo errorsargumento: InOrder inOrder = inOrder(errors);(ver documentos )
GreenhouseVeg
2
¿Qué pasa si el pedido NO es relevante? que suele ser el caso.
haelix
1
@haelix En ese caso, use la respuesta de Brads. Convertir el Listque Sety afirmar que el conjunto de entradas es igual al conjunto dado por las capturas de argumento.
flopshot
25

Intente algo como esto:

verify(errors, times(2))
     .add(AdditionalMatchers.or(eq("exception.message"), eq("exception.detail")),
          any(ActionError.class));
Juan B
fuente
4
Su cheque es obviamente demasiado relajado.
Haelix
17

probablemente tenga un problema en su código. Porque, de hecho, escribes este código:

Map<Character, String> map = mock(Map.class);

map.put('a', "a");
map.put('b', "b");
map.put('c', "c");

verify(map).put(eq('c'), anyString());
verify(map).put(eq('a'), anyString());
verify(map).put(eq('b'), anyString());

Tenga en cuenta que la primera verificación ni siquiera está en orden con respecto a las invocaciones reales.

Además, le recomendaría que no se burle de los tipos que no le pertenecen, por ejemplo, el tipo struts.

[EDITAR @Brad]

Después de ejecutar el código de Brice (arriba) en mi IDE, puedo ver que he usado ActionError en lugar de ActionMessage, por eso mi verify () no coincide. El mensaje de error que publiqué inicialmente me engañó haciéndome pensar que era el primer argumento que no coincidía. Resulta que fue el segundo argumento.

Entonces la respuesta a mi pregunta es

/** 
 * note that ActionMessageFactory.createErrorMessage() returns ActionMessage
 * and ActionError extends ActionMessage
 */
verify(errors).add(eq("exception.message"), any(ActionMessage.class));
verify(errors).add(eq("exception.detail"), any(ActionMessage.class));
Brice
fuente
1
No entiendas lo que intentas decir. ¿Importa el orden de verificación? si la orden de verificación importa. ¿Por qué entonces aquí se proporciona la API de InOrder?
Oleksandr Papchenko
Al igual que lo que está escrito arriba de la orden de verificación es irrelevante; por eso hay InOrder.
Brice
12

Puede usar lo Mockito.atLeastOnce()que le permite a Mockito pasar la prueba incluso si ese mockObject se llamará muchas veces.

Mockito.verify(mockObject, Mockito.atLeastOnce()).testMethod(Mockito.eq(1));

Mockito.verify(mockObject, Mockito.atLeastOnce()).testMethod(Mockito.eq(2));
sendon1982
fuente
1

1) Dile a Mokito la expectativa total de llamadas.

2) Dígale a Mokito cuántas veces se esperaba cada combinación de parámetros.

verify(errors, times(2)).add(any(), any(ActionMessage.class));

verify(errors, atLeastOnce()).add(eq("exception.message"), any());
verify(errors, atLeastOnce()).add(eq("exception.detail"), any());
epoxi
fuente
0

De manera similar a @ sendon1928 podemos usar:

Mockito.times(wantedInvocationCount)

para asegurarse de que se haya llamado al método el número exacto de veces (solución preferible en mi opinión). Luego, podemos llamar

Mockito.verifyNoMoreInteractions(mock)

Para asegurarse de que el simulacro no se use más en ningún contexto. Ejemplo completo:

Mockito.verify(mockObject, Mockito.times(wantedInvocationCount)).testMethod(Mockito.eq(1));

Mockito.verify(mockObject, Mockito.times(wantedInvocationCount)).testMethod(Mockito.eq(2));

Mockito.verifyNoMoreInteractions(mockObject)
Michał Powłoka
fuente