Перейти к основному содержимому

Предотвращение выполнения компонентов ApplicationRunner или CommandLineRunner во время тестирования Junit

· 5 мин. чтения

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 .