steveperkins
Estoy tratando de probar una clase de servicio, que internamente utiliza un objeto de conexión Spring AMQP. Spring inyecta este objeto de conexión. Sin embargo, no quiero que mi prueba unitaria se comunique realmente con el agente de AMQP, por lo que estoy usando Mockito para inyectar una simulación del objeto de conexión.
/**
* The real service class being tested. Has an injected dependency.
*/
public class UserService {
@Autowired
private AmqpTemplate amqpTemplate;
public final String doSomething(final String inputString) {
final String requestId = UUID.randomUUID().toString();
final Message message = ...;
amqpTemplate.send(requestId, message);
return requestId;
}
}
/**
* Unit test
*/
public class UserServiceTest {
/** This is the class whose real code I want to test */
@InjectMocks
private UserService userService;
/** This is a dependency of the real class, that I wish to override with a mock */
@Mock
private AmqpTemplate amqpTemplateMock;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testDoSomething() {
doNothing().when(amqpTemplateMock).send(anyString(), any(Message.class));
// Call the real service class method, which internally will make
// use of the mock (I've verified that this works right).
userService.doSomething(...);
// Okay, now I need to verify that UUID string returned by
// "userService.doSomething(...) matches the argument that method
// internally passed to "amqpTemplateMock.send(...)". Up here
// at the unit test level, how can I capture the arguments passed
// to that inject mock for comparison?
//
// Since the value being compared is a UUID string created
// internally within "userService", I cannot just verify against
// a fixed expected value. The UUID will by definition always be
// unique.
}
}
Se espera que los comentarios en este ejemplo de código expongan la pregunta con claridad. Cuando Mockito inyecta una dependencia simulada en una clase real y las pruebas unitarias en la clase real provocan que se realicen llamadas al simulacro, ¿cómo puede recuperar más tarde los argumentos exactos que se pasaron al simulacro inyectado?
Utilice uno o más ArgumentCaptor
s.
No está claro cuáles son sus tipos aquí, pero de todos modos. Supongamos que tiene un simulacro que tiene un método doSomething()
tomar una Foo
como argumento, entonces haces esto:
final ArgumentCaptor<Foo> captor = ArgumentCaptor.forClass(Foo.class);
verify(mock).doSomething(captor.capture());
final Foo argument = captor.getValue();
// Test the argument
Además, parece que su método devuelve vacío y no quiere que haga nada. Solo escribe esto:
doNothing().when(theMock).doSomething(any());
-
Gracias por el consejo sobre
doAnswer()
contradoNothing()
, he limpiado el ejemplo de código anterior en consecuencia. Sin embargo, no estoy seguro de si me estoy perdiendo algo o si su respuesta aún está un nivel por debajo de mi problema. Cuando empiezo llamandoverify()
en mi objeto simulado ANTES de llamar a la clase real que usa el simulacro internamente, luego aparece el mensaje de excepción: “En realidad, no hubo interacciones con este simulacro”. La ejecución falla en elverify()
línea… y ni siquiera llega al código real que estoy tratando de probar.–Steve Perkins
20 de marzo de 2015 a las 15:32
-
En su ejemplo, ¿está tratando de pasar un argumento a su objeto simulado directamente desde la prueba unitaria? No estoy… Estoy tratando de capturar un argumento que pasa por una capa intermedia entre la prueba unitaria y el objeto simulado. El simulacro está siendo inyectado en este intermediario.
–Steve Perkins
20 de marzo de 2015 a las 15:33
-
¡Ah! El problema que no estaba claro en su fragmento es que el
verify()
llame al simulacro, así como a lagetValue()
llama al captor, ambos vienen DESPUÉS de la invocación del método en el objeto real que se está probando. estaba llamando por errorverify()
antes de la invocación del método de objeto real.–Steve Perkins
20 de marzo de 2015 a las 15:54
-
@StevePerkins, lo siento, no estaba allí, pero veo que lo has descubierto;)
– fge
20 de marzo de 2015 a las 16:51
-
También hay un atajo para crear captor en el nivel de clase: @Captor private ArgumentCaptor
fooCaptor; – ozeray
18 de mayo de 2018 a las 7:35
Kirby
puedes enganchar doAnswer()
al trozo de la send()
método en amqpTemplateMock
y luego capturar los argumentos de invocación de AmqpTemplate.send()
.
Haz la primera línea de tu testDoSomething()
ser esto
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(final InvocationOnMock invocation) {
final Object[] args = invocation.getArguments();
System.out.println("UUID=" + args[0]); // do your assertions here
return null;
}
}).when(amqpTemplateMock).send(Matchers.anyString(), Matchers.anyObject());
poniéndolo todo junto, la prueba se convierte en
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class UserServiceTest {
/** This is the class whose real code I want to test */
@InjectMocks
private UserService userService;
/** This is a dependency of the real class, that I wish to override with a mock */
@Mock
private AmqpTemplate amqpTemplateMock;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testDoSomething() throws Exception {
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(final InvocationOnMock invocation) {
final Object[] args = invocation.getArguments();
System.out.println("UUID=" + args[0]); // do your assertions here
return null;
}
}).when(amqpTemplateMock).send(Matchers.anyString(), Matchers.anyObject());
userService.doSomething(Long.toString(System.currentTimeMillis()));
}
}
Esto da salida
UUID=8e276a73-12fa-4a7e-a7cc-488d1ce0291f
Encontré esto al leer esta publicación, Cómo hacer métodos simulados para anular con mockito
-
Buena respuesta. Además, las versiones recientes de Mockito (estoy usando 2.24.5) incluyen interfaces
Answer1
aAnswer5
para facilitar el uso de invocaciones con hasta cinco argumentos, por ejemplo, para implementarAnswer1<String, Integer>
tienes que proporcionar unpublic String answer(final Integer argument0) throws Throwable
método.– SJuan76
28 de julio de 2021 a las 11:14