1. Обзор
В этом руководстве мы покажем, как мы можем предотвратить запуск bean-компонентов типа ApplicationRunner
или CommandLineRunner
во время интеграционных тестов Spring Boot.
2. Пример приложения
Наш пример приложения состоит из запуска командной строки, запуска приложения и bean-компонента службы задач.
Запуск командной строки вызывает метод выполнения службы задач ,
чтобы выполнить задачу при запуске приложения:
@Component
public class CommandLineTaskExecutor implements CommandLineRunner {
private TaskService taskService;
public CommandLineTaskExecutor(TaskService taskService) {
this.taskService = taskService;
}
@Override
public void run(String... args) throws Exception {
taskService.execute("command line runner task");
}
}
Таким же образом средство запуска приложения взаимодействует со службой задач для выполнения другой задачи:
@Component
public class ApplicationRunnerTaskExecutor implements ApplicationRunner {
private TaskService taskService;
public ApplicationRunnerTaskExecutor(TaskService taskService) {
this.taskService = taskService;
}
@Override
public void run(ApplicationArguments args) throws Exception {
taskService.execute("application runner task");
}
}
Наконец, служба задач отвечает за выполнение задач своего клиента:
@Service
public class TaskService {
private static Logger logger = LoggerFactory.getLogger(TaskService.class);
public void execute(String task) {
logger.info("do " + task);
}
}
И у нас также есть класс приложения Spring Boot, который заставляет все это работать:
@SpringBootApplication
public class ApplicationCommandLineRunnerApp {
public static void main(String[] args) {
SpringApplication.run(ApplicationCommandLineRunnerApp.class, args);
}
}
3. Проверка ожидаемого поведения
ApplicationRunnerTaskExecutor
и CommandLineTaskExecutor запускаются после того
, как Spring Boot загружает контекст приложения.
Мы можем проверить это с помощью простого теста:
@SpringBootTest
class RunApplicationIntegrationTest {
@SpyBean
ApplicationRunnerTaskExecutor applicationRunnerTaskExecutor;
@SpyBean
CommandLineTaskExecutor commandLineTaskExecutor;
@Test
void whenContextLoads_thenRunnersRun() throws Exception {
verify(applicationRunnerTaskExecutor, times(1)).run(any());
verify(commandLineTaskExecutor, times(1)).run(any());
}
}
Как мы видим, мы используем аннотацию SpyBean
для применения шпионов Mockito к компонентам ApplicationRunnerTaskExecutor
и CommandLineTaskExecutor
. Таким образом мы можем убедиться, что метод run
каждого из этих bean-компонентов был вызван один раз.
В следующих разделах мы рассмотрим различные способы и методы предотвращения этого поведения по умолчанию во время наших интеграционных тестов Spring Boot.
4. Профилактика с помощью профилей Spring
Один из способов, которым мы можем предотвратить запуск этих двух, — это аннотировать их с помощью @Profile
:
@Profile("!test")
@Component
public class CommandLineTaskExecutor implements CommandLineRunner {
// same as before
}
@Profile("!test")
@Component
public class ApplicationRunnerTaskExecutor implements ApplicationRunner {
// same as before
}
После вышеуказанных изменений мы приступаем к нашему интеграционному тесту:
@ActiveProfiles("test")
@SpringBootTest
class RunApplicationWithTestProfileIntegrationTest {
@Autowired
private ApplicationContext context;
@Test
void whenContextLoads_thenRunnersAreNotLoaded() {
assertNotNull(context.getBean(TaskService.class));
assertThrows(NoSuchBeanDefinitionException.class,
() -> context.getBean(CommandLineTaskExecutor.class),
"CommandLineRunner should not be loaded during this integration test");
assertThrows(NoSuchBeanDefinitionException.class,
() -> context.getBean(ApplicationRunnerTaskExecutor.class),
"ApplicationRunner should not be loaded during this integration test");
}
}
Как мы видим, мы аннотировали приведенный выше тестовый класс аннотацией @ActiveProfiles("test")
, что означает, что он не будет связывать классы, аннотированные с помощью @Profile("!test")
. В результате ни компонент CommandLineTaskExecutor, ни компонент ApplicationRunnerTaskExecutor
вообще
не загружаются.
5. Предотвращение с помощью аннотации ConditionalOnProperty
Или мы можем настроить их связывание по свойству, а затем использовать аннотацию ConditionalOnProperty :
@ConditionalOnProperty(
prefix = "application.runner",
value = "enabled",
havingValue = "true",
matchIfMissing = true)
@Component
public class ApplicationRunnerTaskExecutor implements ApplicationRunner {
// same as before
}
@ConditionalOnProperty(
prefix = "command.line.runner",
value = "enabled",
havingValue = "true",
matchIfMissing = true)
@Component
public class CommandLineTaskExecutor implements CommandLineRunner {
// same as before
}
Как мы видим, ApplicationRunnerTaskExecutor и
CommandLineTaskExecutor включены
по умолчанию, и мы можем отключить их, если установим для следующих свойств значение false
:
command.line.runner.enabled
приложение.бегун.включено
Итак, в нашем тесте мы установили для этих свойств значение false
, и ни ApplicationRunnerTaskExecutor
, ни бины CommandLineTaskExecutor
не загружаются в контекст приложения :
@SpringBootTest(properties = {
"command.line.runner.enabled=false",
"application.runner.enabled=false" })
class RunApplicationWithTestPropertiesIntegrationTest {
// same as before
}
Теперь, хотя описанные выше методы помогают нам достичь нашей цели, есть случаи, когда мы хотим проверить, что все bean-компоненты Spring загружены и подключены правильно.
Например, мы можем захотеть проверить правильность внедрения bean-компонента TaskService
в CommandLineTaskExecutor,
но мы по-прежнему не хотим, чтобы его метод run выполнялся во время нашего теста.
Итак, давайте посмотрим последний раздел, в котором объясняется, как мы можем этого добиться.
6. Предотвращение путем отказа от начальной загрузки всего контейнера
Здесь мы опишем, как мы можем предотвратить выполнение bean-компонентов CommandLineTaskExecutor
и ApplicationRunnerTaskExecutor
, не загружая весь контейнер приложения.
В предыдущих разделах мы использовали аннотацию @SpringBootTest
, и это привело к загрузке всего контейнера во время наших интеграционных тестов. @SpringBootTest
включает две метааннотации , относящиеся к этому последнему решению:
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
Что ж, если нет необходимости загружать весь контейнер во время нашего теста, то не нужно использовать @BootstrapWith
.
Вместо этого мы можем заменить его на @ContextConfiguration
:
@ContextConfiguration(classes = {ApplicationCommandLineRunnerApp.class},
initializers = ConfigDataApplicationContextInitializer.class)
С помощью @ContextConfiguration
мы определяем, как загружать и настраивать контекст приложения для интеграционных тестов. Установив свойство классов
ContextConfiguration
, мы объявляем, что Spring Boot должен использовать класс ApplicationCommandLineRunnerApp
для загрузки контекста приложения. Определив инициализатор как ConfigDataApplicationContextInitializer
, приложение загружает его свойства .
Нам по-прежнему нужен @ExtendWith(SpringExtension.class)
, так как он интегрирует Spring TestContext Framework в модель программирования JUnit 5 Jupiter. ``
В результате вышеизложенного контекст приложения Spring Boot загружает компоненты и свойства приложения без выполнения bean-компонентов CommandLineTaskExecutor
или ApplicationRunnerTaskExecutor
:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ApplicationCommandLineRunnerApp.class },
initializers = ConfigDataApplicationContextInitializer.class)
public class LoadSpringContextIntegrationTest {
@SpyBean
TaskService taskService;
@SpyBean
CommandLineRunner commandLineRunner;
@SpyBean
ApplicationRunner applicationRunner;
@Test
void whenContextLoads_thenRunnersDoNotRun() throws Exception {
assertNotNull(taskService);
assertNotNull(commandLineRunner);
assertNotNull(applicationRunner);
verify(taskService, times(0)).execute(any());
verify(commandLineRunner, times(0)).run(any());
verify(applicationRunner, times(0)).run(any());
}
}
Кроме того, мы должны иметь в виду, что ConfigDataApplicationContextInitializer ,
когда он используется отдельно, не обеспечивает поддержку внедрения @Value("${…}")
. Если мы хотим его поддерживать, нам нужно настроить PropertySourcesPlaceholderConfigurer
.
7. Заключение
В этой статье мы показали различные способы предотвращения выполнения bean-компонентов ApplicationRunner
и CommandLineRunner
во время интеграционных тестов Spring Boot.
Как всегда, код доступен на GitHub .