Как отключить 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.