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




integration test (20)

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.


Answers

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


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)


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() {
        ....
    }
}

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.


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;
}

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>

@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.


<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


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.


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 .


Puedo hacer lo siguiente usando Mockito:

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

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>

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.


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;
    }
}

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 :)


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.


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.


A partir de la primavera 4.3, @Autowired puede inyectar listas y mapas y el código dado en la pregunta funcionaría:

Dicho esto, a partir de 4.3, los tipos de colecciones / mapas y matrices se pueden emparejar a través del algoritmo de coincidencia de tipos @Autowired de Spring, siempre que la información del tipo de elemento se conserve en firmas de tipo de retorno @Bean o jerarquías de herencia de recopilación.

Pero con una versión de Spring inferior, no puedes autocablear una colección como esa. Sin embargo, puedes hacer lo siguiente:

@Resource(name="AdditionalParams")
private Map<String, String> additionalParams;

o incluso:

@Value("#{AdditionalParams}")
private Map<String, String> additionalParams;

Verifique los documentos de primavera , la sección de consejos:

los beans que se definen a sí mismos como una colección o tipo de mapa no se pueden inyectar a través de @Autowired, porque la coincidencia de tipos no se aplica correctamente a ellos. Use @Resource para tales granos







spring dependency-injection junit annotations mockito