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