java - example - Mockito, JUnit y Spring
mockito verify (7)
Empecé a aprender sobre Mockito solo hoy. Escribí una prueba simple (con JUnit, ver más abajo), pero no puedo entender cómo puedo usar el objeto simulado dentro de Spring''s beans. ¿Cuál es la mejor práctica para trabajar con Spring? ¿Cómo debería inyectar una dependencia burlada a mi bean?
Puedes omitir esto hasta mi pregunta .
Primero que nada, lo que aprendí. Este es un artículo muy bueno. Los Mocks No son los Stubs que explican los conceptos básicos (Verificación del comportamiento de las verificaciones falsas, no verificación del estado ). Entonces hay un buen ejemplo aquí. Mockito Aquí burla más fácil con mockito tenemos una explicación de que los objetos simulados de Mockito son simulacros y trozos .
Aquí Mockito y aquí Matchers puedes encontrar más ejemplos.
Esta prueba
@Test
public void testReal(){
List<String> mockedList = mock(List.class);
//stubbing
//when(mockedList.get(0)).thenReturn("first");
mockedList.get(anyInt());
OngoingStubbing<String> stub= when(null);
stub.thenReturn("first");
//String res = mockedList.get(0);
//System.out.println(res);
//you can also verify using argument matcher
//verify(mockedList).get(anyInt());
verify(mockedList);
mockedList.get(anyInt());
}
funciona bien
De regreso a mi pregunta Aquí Inyectando Mockito se burla de un Spring Bean, alguien trata de usar Springs ReflectionTestUtils.setField()
, pero aquí Pruebas de Integración de Primavera, Creando Objetos Simulados , tenemos la recomendación de cambiar el contexto de Spring.
Realmente no entendí los últimos dos enlaces ... ¿Alguien puede explicarme qué problema tiene Spring con Mockito? ¿Qué pasa con esta solución?
@InjectMocks
private MyTestObject testObject
@Mock
private MyDependentObject mockedObject
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
https://stackoverflow.com/a/8742745/1137529
EDITAR : No estaba muy claro. Proporcionaré 3 ejemplos de código para aclarar mi identidad: supongamos que tenemos bean HelloWorld con el método printHello()
y bean HelloFacade con el método sayHello
que reenvía las llamadas al método de HelloWorld printHello()
.
El primer ejemplo es usar el contexto de Spring y sin el corredor personalizado, usando ReflectionTestUtils para la inyección de dependencia (DI):
public class Hello1Test {
private ApplicationContext ctx;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}
@Test
public void testHelloFacade() {
HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
HelloWorld mock = mock(HelloWorld.class);
doNothing().when(mock).printHello();
ReflectionTestUtils.setField(obj, "hello", mock);
obj.sayHello();
verify(mock, times(1)).printHello();
}
}
Como @Noam señaló que hay una forma de ejecutarlo sin una llamada explícita a MockitoAnnotations.initMocks(this);
. También usaré el contexto de Spring en este ejemplo.
@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {
@InjectMocks
private HelloFacade obj = new HelloFacadeImpl();
@Mock
private HelloWorld mock;
@Test
public void testHelloFacade() {
doNothing().when(mock).printHello();
obj.sayHello();
}
}
Otra forma de hacer esto
public class Hello1aTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@InjectMocks
private HelloFacadeImpl obj;
@Mock
private HelloWorld mock;
@Test
public void testHelloFacade() {
doNothing().when(mock).printHello();
obj.sayHello();
}
}
Noth, que en preivious example tenemos que instatar manualmente HelloFacadeImpl y asignarlo a HelloFacade, porque HelloFacade es la interfaz. En el último ejemplo, podemos declarar HelloFacadeImpl y Mokito lo instanciará por nosotros. El inconveniente de este enfoque es que ahora, unit-under-test es impl-class y no interface.
Aquí está mi breve resumen.
Si desea escribir una prueba unitaria, no use Spring applicationContext porque no desea inyecciones reales inyectadas en la clase que está probando. En su lugar, use @RunWith(MockitoJUnitRunner.class)
, ya sea con la @RunWith(MockitoJUnitRunner.class)
en la parte superior de la clase o con MockitoAnnotations.initMocks(this)
en el método @Before.
Si quiere escribir una prueba de integración, use:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")
Para configurar su contexto de aplicación con una base de datos en memoria, por ejemplo. Normalmente no utiliza simulaciones en pruebas de integración, pero puede hacerlo utilizando el MockitoAnnotations.initMocks(this)
descrito anteriormente.
Honestamente, no estoy seguro de si realmente entiendo su pregunta: PI intentará aclarar todo lo que pueda, por lo que recibí de su pregunta original:
Primero, en la mayoría de los casos, NO debes preocuparte por Spring. Rara vez necesita involucrarse en la escritura de su prueba unitaria. En el caso normal, solo necesita crear una instancia del sistema bajo prueba (SUT, el objetivo que se probará) en su prueba de unidad e inyectar dependencias de SUT en la prueba también. Las dependencias suelen ser un simulacro / stub.
Tu forma original sugerida, y el ejemplo 2, 3 es precisamente hacer lo que estoy describiendo arriba.
En algunos casos excepcionales (como pruebas de integración o algunas pruebas de unidades especiales), debe crear un contexto de la aplicación Spring y obtener su SUT desde el contexto de la aplicación. En tal caso, creo que puedes:
1) Crea tu SUT en spring app ctx, haz referencia a él, e inject mocks a él
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {
@Autowired
@InjectMocks
TestTarget sut;
@Mock
Foo mockFoo;
@Before
/* Initialized mocks */
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void someTest() {
// ....
}
}
o
2) siga el camino descrito en su enlace Pruebas de integración de primavera, Creación de objetos simulados . Este enfoque consiste en crear simulacros en el contexto de la aplicación Spring, y puede obtener el objeto simulado de la aplicación ctx para realizar su anotación / verificación:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {
@Autowired
TestTarget sut;
@Autowired
Foo mockFoo;
@Test
public void someTest() {
// ....
}
}
Ambas formas deberían funcionar. La principal diferencia es que el primer caso tendrá las dependencias inyectadas después de pasar por el ciclo de vida de primavera, etc. (por ejemplo, inicialización de frijol), mientras que el último caso se inyecta antes de las mangueras. Por ejemplo, si su SUT implementa InitializingBean de primavera, y la rutina de inicialización involucra las dependencias, verá la diferencia entre estos dos enfoques. Creo que no está bien o mal para estos 2 enfoques, siempre y cuando sepas lo que estás haciendo.
Solo un suplemento, @Mock, @Inject, MocktoJunitRunner, etc. son innecesarios al usar Mockito. Son solo utilidades que lo ayudarán a escribir el Mockito.mock (Foo.class) y un montón de invocaciones de setter.
La diferencia en si tiene que instanciar su campo anotado @InjectMocks
está en la versión de Mockito, no en si usa MockitoJunitRunner o MockitoAnnotations.initMocks
. En 1.9, que también manejará alguna inyección de constructor de tus campos @Mock
, hará la @Mock
instancias para ti. En versiones anteriores, debe crear una instancia usted mismo.
Así es como hago las pruebas unitarias de mis granos de primavera. No hay ningún problema. Las personas se confunden cuando quieren usar los archivos de configuración de Spring para realizar la inyección de los simulacros, que es cruzar el punto de las pruebas unitarias y las pruebas de integración.
Y, por supuesto, la unidad bajo prueba es un Impl. Tienes que probar una cosa concreta concreta, ¿verdad? Incluso si lo declaraste como una interfaz, tendrías que instanciar el objeto real para probarlo. Ahora, podría entrar en espías, que son envoltorios de stub / mock alrededor de objetos reales, pero eso debería ser para casos de esquina.
La introducción de algunas nuevas instalaciones de prueba en Spring 4.2.RC1 permite escribir pruebas de integración de Spring que no dependen de SpringJUnit4ClassRunner
. Vea this parte de la documentación.
En su caso, podría escribir su prueba de integración de Spring y seguir utilizando burlas como esta:
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {
@ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Autowired
@InjectMocks
TestTarget sut;
@Mock
Foo mockFoo;
@Test
public void someTest() {
// ....
}
}
Realmente no necesitas MockitoAnnotations.initMocks(this);
si está utilizando mockito 1.9 (o más reciente), todo lo que necesita es esto:
@InjectMocks
private MyTestObject testObject;
@Mock
private MyDependentObject mockedObject;
La anotación @InjectMocks
inyectará todos tus MyTestObject
objeto MyTestObject
.
Si migrara su proyecto a Spring Boot 1.4, podría usar la nueva anotación @MockBean
para falsificar MyDependentObject
. Con esa característica, puedes eliminar las anotaciones @Mock
y @InjectMocks
de tu prueba.
Su pregunta parece estar preguntando cuál de los tres ejemplos que ha dado es el enfoque preferido.
El ejemplo 1 utilizando Reflection TestUtils no es un buen enfoque para las pruebas unitarias . Realmente no desea cargar el contexto de primavera para una prueba unitaria. Solo fingir e inyectar lo que se requiere como se muestra en los otros ejemplos.
Desea cargar el contexto de primavera si desea realizar algunas pruebas de integración , sin embargo, prefiero utilizar @RunWith(SpringJUnit4ClassRunner.class)
para realizar la carga del contexto junto con @Autowired
si necesita acceder a sus ''beans explícitamente.
El ejemplo 2 es un enfoque válido y el uso de @RunWith(MockitoJUnitRunner.class)
eliminará la necesidad de especificar un método @Before y una llamada explícita a MockitoAnnotations.initMocks(this);
El ejemplo 3 es otro enfoque válido que no usa @RunWith(...)
. No has instanciado tu clase en prueba HelloFacadeImpl
explícitamente, pero podrías haber hecho lo mismo con el Ejemplo 2.
Mi sugerencia es usar el Ejemplo 2 para las pruebas de su unidad, ya que reduce el desorden del código. Puede recurrir a la configuración más detallada si y cuando se le obliga a hacerlo.