Как отключить autwiring Spring в модульных тестах для использования @Configuration/@Bean
Я хочу настроить компонентный тест с помощью spring -test конфигурации внутреннего класса (@Configuration
). Протестированные компоненты имеют некоторые услуги, которые я хотел бы издеваться над тестом. Эти сервисы являются классами (без использования интерфейса) и содержат spring аннотации (@Autowired
). Мокито может легко их высмеять, однако я не нашел способа отключить автосогласование spring.
Пример того, как я могу легко воспроизвести:
@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;
}
В этом примере "TestBean" представляет собой услугу, которую нужно издеваться. Я бы не хотел, чтобы "бар" вводился spring! @Bean(autowire = NO)
не помогает (на самом деле это значение по умолчанию).
(Пожалуйста, сохраните меня из комментариев "Использовать интерфейсы!" - посмеянный сервис может быть третьим лицом, с которым я ничего не могу сделать.)
UPDATE
Springockito частично решает проблему, если вам не нужно ничего настраивать (поэтому вы не можете использовать класс конфигурации с Springockito - он не поддерживает его), но используйте только mocks.
Все еще ищет чистое решение spring, если есть...
Ответы
Ответ 1
Вот мое решение вашей проблемы:
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();
}
}
}
а затем просто
@Import(MockitoSkipAutowireConfiguration.class)
в вашем тесте @Configuraiton, и вы все настроены
Ответ 2
Я решил это, создав FactoryBean для моего bean вместо того, чтобы просто насмехаться bean. На этом пути Spring не пытайтесь использовать поля autwire.
Factory bean помогая классу:
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;
}
}
Актуальная часть контекста тестирования:
@Configuration
public class TestContext {
@Bean
public FactoryBean<MockingService> mockingService() {
return new MockitoFactoryBean<>(MockingService.class);
}
}
Ответ 3
Вы можете добавить издеваемую службу вручную в контекст приложения spring через org.springframework.beans.factory.config.SingletonBeanRegistry # registerSingleton. Таким образом, макет не обрабатывается после spring, а spring не пытается выполнить аутопроверку макета. Сам макет будет введен в ваш протестированный bean.
@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;
}
}
Ответ 4
Я в той же ситуации.
Что я обнаружил, что если вы не установите загрузчик контекста аннотацией @ContextConfiguration в своем тестовом классе, будет использоваться загрузчик контекста по умолчанию, который получен из AbstractGenericContextLoader. Я посмотрел на его источник и выяснил, что он регистрирует все почтовые процессоры bean, которые отвечают за чтение аннотаций, таких как @Autowired. Другими словами, настройка аннотации включена по умолчанию.
Таким образом, основная проблема заключается в том, что есть две конфигурации, которые находятся в конфликте: в java-конфиге мы сказали, что автоустановка не нужна, в то время как автообновленная аннотация говорит об обратном. Реальный вопрос заключается в том, как отключить обработку аннотаций, чтобы устранить нежелательную конфигурацию.
Насколько я знаю, нет такой реализации spring ContextLoader, которая не была бы получена из AbstractGenericContextLoader, поэтому я думаю, что единственное, что мы можем сделать, это написать нашу собственную. Это будет примерно так:
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);
}
}
Конечно, стоит потратить больше времени, чтобы узнать, как правильно реализовать ContextLoader.
Cheers,
Роберт
Ответ 5
Проверьте Spring профили. Вам не нужно отключать автоматическую проводку, вам нужно вводить различные beans для различной конфигурации.
Ответ 6
Есть так много способов сделать это, я уверен, что этот ответ будет неполным, но вот несколько вариантов...
Как представляется, в настоящее время рекомендуется использовать практику, используйте инсталляцию конструктора для ваших служб, а не непосредственно для автоподготовки полей. Это облегчает тестирование таким образом.
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;
}
}
Таким образом, вы можете также смешать автоуровневые и издевавшиеся сервисы, выполнив аутсорсинг в самом тесте, а затем построив тестируемый beans с помощью наиболее полезного сочетания автоуровневых и издевающихся beans.
Разумной альтернативой является использование профилей Spring для определения сервисов-заглушек. Это особенно полезно, когда вы хотите использовать те же очерченные функции в нескольких тестах:
@Service
@Primary
@Profile("test")
public class MyServiceStub implements MyService {
// ...
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeTest.Beans.class)
@ActiveProfiles({"test"})
public class SomeTest {
// ...
}
Используя аннотацию @Primary
, она гарантирует, что этот заглушка bean будет использоваться вместо любого другого bean, реализующего интерфейс MyService
. Я использую этот подход для таких вещей, как службы электронной почты, где, изменяя профиль, я могу переключаться между реальным почтовым сервером и Wiser.