java spring junit mockito spring-test

java - Cómo deshabilitar el cableado automático de Spring en las pruebas unitarias para el uso de @ Configuration/@ Bean



junit mockito (6)

Aquí está mi solución a su problema:

import static org.mockito.Mockito.mockingDetails; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MockitoSkipAutowireConfiguration { @Bean MockBeanFactory mockBeanFactory() { return new MockBeanFactory(); } private static class MockBeanFactory extends InstantiationAwareBeanPostProcessorAdapter { @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { return !mockingDetails(bean).isMock(); } } }

y luego solo

@Import(MockitoSkipAutowireConfiguration.class)

en su prueba @Configuration y ya está todo listo

Quiero configurar una prueba de componentes utilizando la clase interna de configuración de prueba de primavera ( @Configuration ). Los componentes probados tienen algunos servicios que me gustaría simular para la prueba. Estos servicios son clases (sin interfaz) y tienen anotaciones de resorte ( @Autowired ) en ellas. Mockito puede burlarse de ellos fácilmente, sin embargo, no encontré ninguna manera de deshabilitar el cableado automático del resorte.

Ejemplo de cómo puedo reproducir fácilmente:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SomeTest.Beans.class) public class SomeTest { // configured in component-config.xml, using ThirdPartyService @Autowired private TestedBean entryPoint; @Test public void test() { } @Configuration @ImportResource("/spring/component-config.xml") static class Beans { @Bean ThirdPartyService createThirdPartyService() { return mock(ThirdPartyService.class); } } } public class ThirdPartyService { @Autowired Foo bar; } public class TestedBean { @Autowired private ThirdPartyService service; }

En este ejemplo, "TestBean" representa el servicio a simular. ¡NO me gustaría que la "barra" se inyectara en la primavera! @Bean(autowire = NO) no ayuda (de hecho, ese es el valor predeterminado). (Por favor, sálvame de los comentarios de "¡usa interfaces!": El servicio simulado puede ser un tercero con el que no puedo hacer nada.)

ACTUALIZAR

Springockito resuelve el problema parcialmente, siempre y cuando no tenga que configurar nada más (por lo que no puede usar la clase de configuración con Springockito, no lo admite), sino usar simulacros solamente. Sigo buscando una solución pura de primavera, si hay alguna ...


Consulta los perfiles de primavera . No necesita deshabilitar el cableado automático, necesita inyectar diferentes beans para una configuración diferente.


Estoy en la misma situación.

Lo que encontré es que si no configura el cargador de contexto mediante la anotación @ContextConfiguration en su clase de prueba, se usará el cargador de contexto predeterminado, que se deriva de AbstractGenericContextLoader. Eché un vistazo a su fuente y resultó que registra todos los postprocesadores de frijoles que son responsables de leer anotaciones como @Autowired. En otras palabras, la configuración de anotación está habilitada por defecto.

Por lo tanto, el problema principal es que hay dos configuraciones que están en conflicto: en la configuración de java dijimos que no se necesita el cableado automático, mientras que la anotación autorizada dice lo contrario. La pregunta real es cómo deshabilitar el proceso de anotación para eliminar la configuración no deseada.

Por lo que sé, no existe tal implementación de ContextLoader que no se derive de AbstractGenericContextLoader, así que supongo que lo único que podemos hacer es escribir el nuestro. Sería algo como esto:

public static class SimpleContextLoader implements ContextLoader { @Override public String[] processLocations(Class<?> type, String... locations) { return strings; } @Override public ApplicationContext loadContext(String... locations) throws Exception { // in case of xml configuration return new ClassPathXmlApplicationContext(strings); // in case of java configuration (but its name is quite misleading) // return new AnnotationConfigApplicationContext(TestConfig.class); } }

Por supuesto, valdría la pena dedicar más tiempo a descubrir cómo implementar ContextLoader correctamente.

Aclamaciones,
Robert


Hay tantas formas de hacer esto, estoy bastante seguro de que esta respuesta será incompleta, pero aquí hay algunas opciones ...

Como parece ser la práctica recomendada actualmente , use la inyección de constructor para sus servicios en lugar de auto cablear los campos directamente. Esto hace que las pruebas como esta sean mucho más fáciles.

public class SomeTest { @Mock private ThirdPartyService mockedBean; @Before public void init() { initMocks(this); } @Test public void test() { BeanUnderTest bean = new BeanUnderTest(mockedBean); // ... } } public class BeanUnderTest{ private ThirdPartyService service; @Autowired public BeanUnderTest(ThirdPartyService ThirdPartyService) { this.thirdPartyService = thirdPartyService; } }

Al hacer eso, también puede mezclar los servicios autowired y simulados al autowiring en la propia prueba y luego construir los beans a prueba con la combinación más útil de beans autowired y simulados.

Una alternativa razonable es usar los perfiles de Spring para definir los servicios de stub. Esto es particularmente útil cuando se desea utilizar las mismas características de código auxiliar en varias pruebas:

@Service @Primary @Profile("test") public class MyServiceStub implements MyService { // ... } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SomeTest.Beans.class) @ActiveProfiles({"test"}) public class SomeTest { // ... }

Al utilizar la anotación @Primary , se asegura que se usará este bean de código auxiliar en lugar de cualquier otro bean que implemente la interfaz MyService . Tiendo a usar este enfoque para cosas como los servicios de correo electrónico, donde al cambiar el perfil, puedo cambiar entre un servidor de correo real y Wiser.


Lo resolví creando FactoryBean para mi bean en lugar de solo burlarse de bean. De esta manera la primavera no trata de autowire los campos.

Fábrica que ayuda a la clase:

public class MockitoFactoryBean<T> implements FactoryBean<T> { private final Class<T> clazz; public MockitoFactoryBean(Class<T> clazz) { this.clazz = clazz; } @Override public T getObject() throws Exception { return mock(clazz); } @Override public Class<T> getObjectType() { return clazz; } @Override public boolean isSingleton() { return true; } }

Parte de contexto de prueba real:

@Configuration public class TestContext { @Bean public FactoryBean<MockingService> mockingService() { return new MockitoFactoryBean<>(MockingService.class); } }


Puede agregar el servicio simulado manualmente al contexto de la aplicación spring a través de org.springframework.beans.factory.config.SingletonBeanRegistry # registerSingleton. De esta manera, el simulacro no se procesa posteriormente en la primavera, y la primavera no intenta autoencontrar el simulacro. El simulacro mismo será inyectado en su frijol probado.

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SomeTest.Beans.class) public class SomeTest { // configured in component-config.xml, using ThirdPartyService @Autowired private TestedBean entryPoint; @Autowired private ThirdPartyService thirdPartyServiceMock; @Test public void test() { } @Configuration static class Beans { @Autowired private GenericApplicationContext ctx; @Bean TestedBean testedBean() { ctx.getBeanFactory().registerSingleton("thirdPartyService", mock(ThirdPartyService.class)); return new TestedBean(); } } public static class ThirdPartyService { @Autowired Object bar; } public static class TestedBean { @Autowired private ThirdPartyService service; } }