test mockitojunitrunner mockbean example spring dependency-injection junit annotations mockito

spring - mockitojunitrunner - La inyección de Mockito se burla en un frijol de primavera



spring boot test repository (23)

A partir de la primavera 3.2, esto ya no es un problema. Spring ahora admite autowiring de los resultados de métodos de fábrica genéricos. Consulte la sección titulada "Métodos de fábrica genéricos" en esta publicación del blog: spring.io/blog/2012/11/07/… .

El punto clave es:

En la primavera 3.2, los tipos de retorno genéricos para los métodos de fábrica ahora se deducen correctamente, y el cableado automático por tipo para simulacros debería funcionar como se esperaba. Como resultado, es probable que ya no sean necesarios soluciones personalizadas, como MockitoFactoryBean, EasyMockFactoryBean o Springockito.

Lo que significa que esto debería funcionar fuera de la caja:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean>

Me gustaría inyectar un objeto simulado de Mockito en un frijol Spring (3+) para realizar pruebas de unidad con JUnit. Mis dependencias de bean se inyectan actualmente mediante el uso de la anotación @Autowired en campos de miembros privados.

He considerado usar ReflectionTestUtils.setField pero la instancia de bean que deseo inyectar es en realidad un proxy y, por lo tanto, no declara los campos de miembros privados de la clase objetivo. No deseo crear un configurador público para la dependencia, ya que luego modificaré mi interfaz únicamente con el propósito de realizar pruebas.

He seguido algunos advice dados por la comunidad de Spring, pero el simulacro no se crea y el cableado automático falla:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean>

El error que encuentro actualmente es el siguiente:

... Caused by: org...NoSuchBeanDefinitionException: No matching bean of type [com.package.Dao] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: { @org...Autowired(required=true), @org...Qualifier(value=dao) } at org...DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(D...y.java:901) at org...DefaultListableBeanFactory.doResolveDependency(D...y.java:770)

Si configuro el valor constructor-arg en algo no válido, no se produce ningún error al iniciar el contexto de la aplicación.


Dado:

@Service public class MyService { @Autowired private MyDAO myDAO; // etc }

Puede hacer que la clase que se está probando se cargue a través de autowiring, simular la dependencia con Mockito y luego usar los ReflectionTestUtils de Spring para inyectar el simulacro en la clase que se está probando.

@ContextConfiguration(classes = { MvcConfiguration.class }) @RunWith(SpringJUnit4ClassRunner.class) public class MyServiceTest { @Autowired private MyService myService; private MyDAO myDAOMock; @Before public void before() { myDAOMock = Mockito.mock(MyDAO.class); ReflectionTestUtils.setField(myService, "myDAO", myDAOMock); } // etc }

Tenga en cuenta que antes de la primavera 4.3.1, este método no funcionará con servicios detrás de un proxy (anotado con @Transactional o Cacheable , por ejemplo). Esto ha sido arreglado por SPR-14050 .

Para versiones anteriores, una solución es desenvolver el proxy, como se describe aquí: la anotación transaccional evita que los servicios se simulen (que es lo que hace ReflectionTestUtils.setField de forma predeterminada ahora)


Desarrollé una solución basada en la propuesta de Kresimir Nesek. Agregué una nueva anotación @EnableMockedBean para hacer que el código sea un poco más limpio y modular.

@EnableMockedBean @SpringBootApplication @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes=MockedBeanTest.class) public class MockedBeanTest { @MockedBean private HelloWorldService helloWorldService; @Autowired private MiddleComponent middleComponent; @Test public void helloWorldIsCalledOnlyOnce() { middleComponent.getHelloMessage(); // THEN HelloWorldService is called only once verify(helloWorldService, times(1)).getHelloMessage(); } }

He escrito un post explicándolo.


Desde 1.8.3 Mockito tiene @InjectMocks - esto es increíblemente útil. Mis pruebas de JUnit son @RunWith MockitoJUnitRunner y construyo objetos @Mock que satisfacen todas las dependencias para la clase que se está probando, las cuales se inyectan cuando el miembro privado está anotado con @InjectMocks.

I @RunWith SpringJUnit4Runner para pruebas de integración solo ahora.

Observaré que no parece poder inyectar la Lista de la misma manera que Spring. Busca solo un objeto simulado que satisfaga la lista y no inyectará una lista de objetos simulados. La solución para mí fue usar un @Spy contra una lista creada de forma manual, y agregar manualmente los objetos simulados a esa lista para la prueba de la unidad. Tal vez eso fue intencional, porque ciertamente me obligó a prestar mucha atención a lo que se burlaban juntos.


El código siguiente funciona con el cableado automático: no es la versión más corta, pero es útil cuando debería funcionar solo con los tarros de resorte / mockito estándar.

<bean id="dao" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> </property> <property name="proxyInterfaces"> <value>com.package.Dao</value> </property> </bean>


En cuanto al ritmo de desarrollo de Springockito y la cantidad de temas pendientes , estaría un poco preocupado por introducirlo en mi pila de pruebas en la actualidad. El hecho de que el último lanzamiento se realizó antes del lanzamiento de Spring 4 presenta preguntas como "¿Es posible integrarlo fácilmente con Spring 4?". No lo sé, porque no lo probé. Prefiero el enfoque de Spring puro si necesito burlarme de los frijoles Spring en la prueba de integración.

Hay una opción para falsificar Spring Bean con solo características de Spring. @Primary usar las @Primary , @Profile y @ActiveProfiles para ello. Escribí una entrada de blog sobre el tema.


Encontré una respuesta similar a la de teabot para crear un MockFactory que ofrece los simulacros. Utilicé el siguiente ejemplo para crear la fábrica de simulacros (ya que el enlace a narkisr está muerto): http://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/org/randompage/bookmarking/backend/testUtils/MocksFactory.java

<bean id="someFacade" class="nl.package.test.MockFactory"> <property name="type" value="nl.package.someFacade"/> </bean>

Esto también ayuda a evitar que Spring quiera resolver las inyecciones del frijol simulado.


Hoy descubrí que un contexto de primavera en el que declaré antes de los frijoles Mockito, no se podía cargar. Después de mover DESPUÉS de los simulacros, el contexto de la aplicación se cargó correctamente. Cuídate :)


La mejor manera es:

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean>

Actualizar
En el archivo de contexto, este simulacro debe aparecer en la lista antes de que se declare cualquier campo de conexión automática, dependiendo de este.


Para que quede constancia, todas mis pruebas funcionan correctamente simplemente haciendo que el dispositivo se inicie de forma perezosa, por ejemplo:

<bean id="fixture" class="it.tidalwave.northernwind.rca.embeddedserver.impl.DefaultEmbeddedServer" lazy-init="true" /> <!-- To solve Mockito + Spring problems --> <bean class="it.tidalwave.messagebus.aspect.spring.MessageBusAdapterFactory" /> <bean id="applicationMessageBus" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="it.tidalwave.messagebus.MessageBus" /> </bean> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="javax.servlet.ServletContext" /> </bean>

Supongo que la razón es la que Mattias explica http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/ (en la parte inferior de la publicación), que una solución está cambiando el orden en que se declaran los beans: la inicialización perezosa es "una especie de" cuando el dispositivo se declara al final.


Publicando algunos ejemplos basados ​​en los enfoques anteriores

Con la primavera:

@ContextConfiguration(locations = { "classpath:context.xml" }) @RunWith(SpringJUnit4ClassRunner.class) public class TestServiceTest { @InjectMocks private TestService testService; @Mock private TestService2 testService2; }

Sin primavera:

@RunWith(MockitoJUnitRunner.class) public class TestServiceTest { @InjectMocks private TestService testService = new TestServiceImpl(); @Mock private TestService2 testService2; }


Puedo hacer lo siguiente usando Mockito:

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.abcd.StateMachine"/> </bean>


Quizás no sea la solución perfecta, pero tiendo a no usar Spring para hacer DI para pruebas unitarias. las dependencias para un solo bean (la clase bajo prueba) por lo general no son demasiado complejas, así que simplemente hago la inyección directamente en el código de prueba.


Si está usando spring> = 3.0 , intente usar la anotación @Configuration Springs para definir parte del contexto de la aplicación

@Configuration @ImportResource("com/blah/blurk/rest-of-config.xml") public class DaoTestConfiguration { @Bean public ApplicationService applicationService() { return mock(ApplicationService.class); } }

Si no desea utilizar @ImportResource, también puede hacerlo al revés:

<beans> <!-- rest of your config --> <!-- the container recognize this as a Configuration and adds it''s beans to the container --> <bean class="com.package.DaoTestConfiguration"/> </beans>

Para obtener más información, eche un vistazo a spring-framework-reference: configuración de contenedor basada en Java


Si está utilizando Spring Boot 1.4, tiene una forma increíble de hacerlo. Simplemente use la nueva marca @SpringBootTest en su clase y @MockBean en el campo y Spring Boot creará un simulacro de este tipo y lo inyectará en el contexto (en lugar de inyectar el original):

@RunWith(SpringRunner.class) @SpringBootTest public class MyTests { @MockBean private RemoteService remoteService; @Autowired private Reverser reverser; @Test public void exampleTest() { // RemoteService has been injected into the reverser bean given(this.remoteService.someCall()).willReturn("mock"); String reverse = reverser.reverseSomeCall(); assertThat(reverse).isEqualTo("kcom"); } }

Por otro lado, si no está usando Spring Boot o está usando una versión anterior, tendrá que hacer un poco más de trabajo:

Cree un bean @Configuration que inyecte sus @Configuration en el contexto de Spring:

@Configuration @Profile("useMocks") public class MockConfigurer { @Bean @Primary public MyBean myBeanSpy() { return mock(MyBean.class); } }

Al @Primary anotación @Primary le está diciendo a Spring que este bean tiene prioridad si no se especifica ningún calificador.

Asegúrese de anotar la clase con @Profile("useMocks") para controlar qué clases usarán el simulacro y cuáles usarán el bean real.

Finalmente, en tu prueba, activa el perfil de userMocks :

@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class}) @WebIntegrationTest @ActiveProfiles(profiles={"useMocks"}) public class YourIntegrationTestIT { @Inject private MyBean myBean; //It will be the mock! @Test public void test() { .... } }

Si no desea utilizar el simulacro sino el bean real, simplemente no active el perfil de useMocks :

@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class}) @WebIntegrationTest public class AnotherIntegrationTestIT { @Inject private MyBean myBean; //It will be the real implementation! @Test public void test() { .... } }


Si utiliza la inyección del controlador, asegúrese de que sus variables locales NO sean "finales"


Sugeriría migrar su proyecto a Spring Boot 1.4. Después de eso, puede usar la nueva anotación @MockBean para falsificar su com.package.Dao


Tengo una solución muy simple usando Spring Java Config y Mockito:

@Configuration public class TestConfig { @Mock BeanA beanA; @Mock BeanB beanB; public TestConfig() { MockitoAnnotations.initMocks(this); //This is a key } //You basically generate getters and add @Bean annotation everywhere @Bean public BeanA getBeanA() { return beanA; } @Bean public BeanB getBeanB() { return beanB; } }


Utilizo una combinación del enfoque utilizado en la respuesta de Markus T y una implementación sencilla de ImportBeanDefinitionRegistrar que busca una anotación personalizada ( @MockedBeans ) en la que se puede especificar qué clases se deben simular. Creo que este enfoque da como resultado una prueba unitaria concisa con parte del código repetitivo relacionado con la eliminación del simulacro.

Así es como se ve una prueba de unidad de muestra con ese enfoque:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader=AnnotationConfigContextLoader.class) public class ExampleServiceIntegrationTest { //our service under test, with mocked dependencies injected @Autowired ExampleService exampleService; //we can autowire mocked beans if we need to used them in tests @Autowired DependencyBeanA dependencyBeanA; @Test public void testSomeMethod() { ... exampleService.someMethod(); ... verify(dependencyBeanA, times(1)).someDependencyMethod(); } /** * Inner class configuration object for this test. Spring will read it thanks to * @ContextConfiguration(loader=AnnotationConfigContextLoader.class) annotation on the test class. */ @Configuration @Import(TestAppConfig.class) //TestAppConfig may contain some common integration testing configuration @MockedBeans({DependencyBeanA.class, DependencyBeanB.class, AnotherDependency.class}) //Beans to be mocked static class ContextConfiguration { @Bean public ExampleService exampleService() { return new ExampleService(); //our service under test } } }

Para que esto suceda, debe definir dos clases simples de ayuda: la anotación personalizada ( @MockedBeans ) y una implementación personalizada ImportBeanDefinitionRegistrar . @MockedBeans definición de anotación de @MockedBeans debe anotarse con @Import(CustomImportBeanDefinitionRegistrar.class) y ImportBeanDefinitionRgistrar debe agregar definiciones de beans ImportBeanDefinitionRgistrar a la configuración en su método registerBeanDefinitions .

Si te gusta el enfoque, puedes encontrar ejemplos de implementations en mi blogpost .


Actualización: ahora hay soluciones mejores y más limpias para este problema. Por favor considere las otras respuestas primero.

Finalmente encontré una respuesta a esto por Ronen en su blog. El problema que estaba teniendo se debe al método Mockito.mock(Class c) declara un tipo de retorno de Object . En consecuencia, Spring no puede inferir el tipo de bean del tipo de retorno de método de fábrica.

La solución de Ronen es crear una implementación de FactoryBean que devuelva simulacros. La interfaz de FactoryBean permite a Spring consultar el tipo de objetos creados por el bean de fábrica.

Mi definición de frijol burlado ahora se ve como:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory"> <property name="type" value="com.package.Dao" /> </bean>


Actualización : nueva respuesta aquí: https://.com/a/19454282/411229 . Esta respuesta solo se aplica a las versiones Spring antes de 3.2.

He buscado por un tiempo una solución más definitiva a esto. Esta publicación de blog parece cubrir todas mis necesidades y no se basa en el pedido de declaraciones de frijoles. Todo el crédito a Mattias Severson. http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/

Básicamente, implementar un FactoryBean.

package com.jayway.springmock; import org.mockito.Mockito; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating mocked beans based on Mockito so that they * can be {@link @Autowired} into Spring test configurations. * * @author Mattias Severson, Jayway * * @see FactoryBean * @see org.mockito.Mockito */ public class MockitoFactoryBean<T> implements FactoryBean<T> { private Class<T> classToBeMocked; /** * Creates a Mockito mock instance of the provided class. * @param classToBeMocked The class to be mocked. */ public MockitoFactoryBean(Class<T> classToBeMocked) { this.classToBeMocked = classToBeMocked; } @Override public T getObject() throws Exception { return Mockito.mock(classToBeMocked); } @Override public Class<?> getObjectType() { return classToBeMocked; } @Override public boolean isSingleton() { return true; } }

A continuación, actualice su configuración de primavera con lo siguiente:

<beans...> <context:component-scan base-package="com.jayway.example"/> <bean id="someDependencyMock" class="com.jayway.springmock.MockitoFactoryBean"> <constructor-arg name="classToBeMocked" value="com.jayway.example.SomeDependency" /> </bean> </beans>


<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory"> <property name="type" value="com.package.Dao" /> </bean>

este ^ funciona perfectamente bien si se declara primero / temprano en el archivo XML. Mockito 1.9.0 / Primavera 3.0.5


@InjectMocks private MyTestObject testObject; @Mock private MyDependentObject mockedObject; @Before public void setup() { MockitoAnnotations.initMocks(this); }

Esto inyectará cualquier objeto simulado en la clase de prueba. En este caso, inyectará mockedObject en el testObject. Esto fue mencionado anteriormente pero aquí está el código.