Для @SpringBootTest @Import класса @TestConfiguration ничего не делает, а @ContextConfiguration переопределяет, как ожидалось

Учитывая следующие аннотации интеграционных тестов:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
                properties = "spring.main.allow-bean-definition-overriding=true")
@ContextConfiguration(classes = {WorkerTestConfig.class})
//@Import(value = {WorkerTestConfig.class})
@ActiveProfiles({"dev","test"})
public class NumberServiceITest {

Роль WorkestTestConfig состоит в том, чтобы переопределить реальный bean-компонент / набор beans во время запуска интеграции, всякий раз, когда я использую @ContextConfiguration, реальный bean-компонент отключается, а тот, который используется в WorkerTestConfig, всякий раз, когда я использую @Import, настоящий bean-компонент все еще создается и не проходит тест .

Сам WorkerTestConfig максимально тривиален:

@TestConfiguration
public class WorkerTestConfig {

    @Primary
    @Bean
    public ScheduledExecutorService taskExecutor() {
        return DirectExecutorFactory.createSameThreadExecutor();
    }
}

может кто-нибудь объяснить, пожалуйста, еще одно волшебное поведение аннотации @SpringBootTest? Если вы воспроизводите такое же поведение, подтвердите, чтобы я мог перейти к системе отслеживания проблем, поскольку я видел людей, использующих @Import с @SpringBootTest здесь, на SO, и ничего не запрещает это в весенних загрузочных документах: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-excluding-config.

Совершенно озадачен тем, что происходит.

Версия: 2.1.2.RELEASE

Обновление:

Также попытался удалить настоящий bean-компонент, чтобы увидеть, связана ли проблема с простым переопределением, но аннотация @Import просто мертва в воде, не работает -> невозможно даже создать bean-компонент, @ContextConfiguration имеет аддитивное / переопределяющее поведение, импорт ничего не делает вообще. Полностью квалифицированный импорт аннотации: import org.springframework.context.annotation.Import;

Тоже пробовал просто ради этого поменять с @TestConfiguration на @Configuration, вообще ничего. МЕРТВЫХ.

Обновление 2:

Однако @Import работает со стандартным тестом пружины:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {Some.class,
                                 Some2WhichDependsOnWorkerTestConfig.class})

@Import(WorkerTestConfig.class)
@ActiveProfiles("test")
public class SomeOtherTest {

person Aubergine    schedule 24.03.2019    source источник
comment
Классы @TestConfiguration будут автоматически зарегистрированы, если они являются вложенными классами. Вы пробовали это?   -  person ck1    schedule 24.03.2019
comment
@ ck1 WorkerConfig - это уровень 5, такой же, как и у теста, они находятся в родственных пакетах. Я не хочу, чтобы это был вложенный класс, потому что он имеет большой потенциал для повторного использования в интеграционных тестах. Ах, вы редактировали, да, как я уже сказал, я хотел бы повторно использовать этот конфиг.   -  person Aubergine    schedule 24.03.2019
comment
Я отказываюсь от ответа. Вы правы: @Import не работает с @TestConfiguration классами верхнего уровня, хотя в документации указано, что должно.   -  person ck1    schedule 24.03.2019
comment
Я предполагаю, что MyTestsConfiguration в @Import(MyTestsConfiguration.class) в разделе 46.3.3 относится к классу, помеченному @TestConfiguration.   -  person ck1    schedule 24.03.2019
comment
круто, я буду ссылаться на SO на трекере проблем Spring   -  person Aubergine    schedule 26.03.2019
comment
@AndyWilkinson подойдет, спасибо   -  person Aubergine    schedule 27.03.2019
comment
@AndyWilkinson Я прикрепил проект к закрытой проблеме в github, так как изначально я не мог воспроизвести, мне пришлось использовать полноценную структуру, так как я не знал точно, что искал. В конце концов, это происходит только для ScheduledExecutorService, то есть в предоставленном мною zip-архиве вы увидите, что я ожидаю вызова имитации, но при использовании аннотации Import вместо этого настраивается реальный ScheduledExecutorService.   -  person Aubergine    schedule 13.04.2019


Ответы (1)


Порядок обработки @Import классов при их использовании в тестах не определен. Функция @Import для тестов была в первую очередь добавлена ​​для упрощения регистрации дополнительных bean-компонентов, она не предназначалась для использования замены определений bean-компонентов.

Если вы хотите покопаться в зарослях и посмотреть, что именно происходит, вы можете открыть ConfigurationClassParser и добавить условную точку останова в doProcessConfigurationClass. Добавьте следующий код условия:

System.err.println(configClass);
return false;

Теперь, если вы отлаживаете приложение, вы получите дополнительный вывод по мере обработки классов конфигурации.

При использовании атрибута аннотации classes без @Import вы увидите:

ConfigurationClass: beanName 'demoImportBugApplication', com.example.demoimportbug.DemoImportBugApplication
ConfigurationClass: beanName 'original', class path resource [com/example/demoimportbug/first/Original.class]
ConfigurationClass: beanName 'workerConfig', class path resource [com/example/demoimportbug/first/WorkerConfig.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'someTestSecondConfiguration', com.example.demoimportbug.second.SomeTestSecondConfiguration
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/NoOpCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class]

Когда вы используете @Import без атрибута classes, вы получите:

ConfigurationClass: beanName 'org.springframework.boot.test.context.ImportsContextCustomizer$ImportsConfiguration', org.springframework.boot.test.context.ImportsContextCustomizer$ImportsConfiguration
ConfigurationClass: beanName 'null', class path resource [com/example/demoimportbug/first/SomeFirstUsingSecondConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [com/example/demoimportbug/second/SomeTestSecondConfiguration.class]
ConfigurationClass: beanName 'demoImportBugApplication', com.example.demoimportbug.DemoImportBugApplication
ConfigurationClass: beanName 'original', class path resource [com/example/demoimportbug/first/Original.class]
ConfigurationClass: beanName 'workerConfig', class path resource [com/example/demoimportbug/first/WorkerConfig.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/scheduling/annotation/ProxyAsyncConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/GenericCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/SimpleCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/cache/NoOpCacheConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class]
ConfigurationClass: beanName 'null', class path resource [org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class]

Первая версия загружает WorkerConfig перед SomeTestSecondConfiguration, тогда как вторая версия загружает SomeTestSecondConfiguration перед WorkerConfig.

Вы также заметите, что во второй версии есть класс ImportsContextCustomizer$ImportsConfiguration, который запускает дополнительный импорт.

Если вы посмотрите на SpringBootTestContextBootstrapper, вы увидите в методе getOrFindConfigurationClasses, что порядок определен, и ваши дополнительные тестовые классы всегда будут перечислены после основной конфигурации.

tl; dr Если вам нужен определенный порядок, используйте атрибут classes. Если вы хотите добавить дополнительные компоненты и ничего не пытаетесь переопределить, используйте @Import.

Вы также можете посмотреть @MockBean, который предоставляет более надежный способ заменить bean-компонент на макет.

person Phil Webb    schedule 13.04.2019
comment
@ phil.webb. спасибо за четкое объяснение и замысел аннотации, а также за удобный трюк, чтобы увидеть, что загружается. Единственное, что меня смущает, - это то, что ScheduledExecutorService следует правилам, которые вы объяснили, и порядку. OriginalInterface последовательно переопределяется bean-компонентом TestConfiguration при импорте. Не могли бы вы проверить второй метод checkWhosGreeting. Когда я использовал точку останова по условию, я вижу, что оригинал загружается после того, как он еще не переопределяет тестовый, bean-компонент ScheduledExecutorService работает последовательно несовместимо со стандартным bean-компонентом :) - person Aubergine; 13.04.2019