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

Руководство по ApplicationContextRunner в Spring Boot

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

1. Обзор

Хорошо известно, что автоконфигурация — одна из ключевых функций Spring Boot , но тестирование сценариев автоконфигурации может оказаться сложной задачей.

В следующих разделах мы покажем, как ApplicationContextRunner упрощает тестирование автоматической конфигурации. ``

2. Протестируйте сценарии автоматической настройки

ApplicationContextRunner — это служебный класс, который запускает ApplicationContext и предоставляет утверждения в стиле AssertJ . Его лучше всего использовать как поле в тестовом классе для общей конфигурации, и впоследствии мы вносим настройки в каждый тест:

private final ApplicationContextRunner contextRunner 
= new ApplicationContextRunner();

Давайте перейдем к демонстрации его магии, протестировав несколько случаев.

2.1. Состояние тестового класса

В этом разделе мы собираемся протестировать некоторые классы автоматической настройки, которые используют аннотации @ConditionalOnClass и @ConditionalOnMissingClass :

@Configuration
@ConditionalOnClass(ConditionalOnClassIntegrationTest.class)
protected static class ConditionalOnClassConfiguration {
@Bean
public String created() {
return "This is created when ConditionalOnClassIntegrationTest "
+ "is present on the classpath";
}
}

@Configuration
@ConditionalOnMissingClass(
"com.foreach.autoconfiguration.ConditionalOnClassIntegrationTest"
)
protected static class ConditionalOnMissingClassConfiguration {
@Bean
public String missed() {
return "This is missed when ConditionalOnClassIntegrationTest "
+ "is present on the classpath";
}
}

Мы хотели бы проверить, правильно ли автоконфигурация создает экземпляры или пропускает созданные и пропущенные bean-компоненты с учетом ожидаемых условий.

ApplicationContextRunner предоставляет нам метод withUserConfiguration , в котором мы можем обеспечить автоматическую настройку по запросу для настройки ApplicationContext для каждого теста.

Метод run принимает ContextConsumer в качестве параметра, который применяет утверждения к контексту. ApplicationContext будет автоматически закрыт при выходе из теста:

@Test
public void whenDependentClassIsPresent_thenBeanCreated() {
this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)
.run(context -> {
assertThat(context).hasBean("created");
assertThat(context.getBean("created"))
.isEqualTo("This is created when ConditionalOnClassIntegrationTest "
+ "is present on the classpath");
});
}

@Test
public void whenDependentClassIsPresent_thenBeanMissing() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)
.run(context -> {
assertThat(context).doesNotHaveBean("missed");
});
}

В предыдущем примере мы видим простоту тестирования сценариев, в которых определенный класс присутствует в пути к классам. Но как мы собираемся проверить обратное, когда класс отсутствует в пути к классам?

Здесь в дело вступает FilteredClassLoader . Он используется для фильтрации указанных классов в пути к классам во время выполнения:

@Test
public void whenDependentClassIsNotPresent_thenBeanMissing() {
this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)
.withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))
.run((context) -> {
assertThat(context).doesNotHaveBean("created");
assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);
});
}

@Test
public void whenDependentClassIsNotPresent_thenBeanCreated() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)
.withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))
.run((context) -> {
assertThat(context).hasBean("missed");
assertThat(context).getBean("missed")
.isEqualTo("This is missed when ConditionalOnClassIntegrationTest "
+ "is present on the classpath");
assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);
});
}

2.2. Состояние тестового компонента

Мы только что рассмотрели тестирование аннотаций @ConditionalOnClass и @ConditionalOnMissingClass , теперь давайте посмотрим, как все выглядит, когда мы используем аннотации @ConditionalOnBean и @ConditionalOnMissingBean .

Для начала нам также понадобятся несколько классов автоконфигурации :

@Configuration
protected static class BasicConfiguration {
@Bean
public String created() {
return "This is always created";
}
}
@Configuration
@ConditionalOnBean(name = "created")
protected static class ConditionalOnBeanConfiguration {
@Bean
public String createOnBean() {
return "This is created when bean (name=created) is present";
}
}
@Configuration
@ConditionalOnMissingBean(name = "created")
protected static class ConditionalOnMissingBeanConfiguration {
@Bean
public String createOnMissingBean() {
return "This is created when bean (name=created) is missing";
}
}

Затем мы вызываем метод withUserConfiguration , как в предыдущем разделе, и отправляем наш пользовательский класс конфигурации, чтобы проверить, правильно ли автоконфигурация создает или пропускает bean-компоненты createOnBean или createOnMissingBean в различных условиях :

@Test
public void whenDependentBeanIsPresent_thenConditionalBeanCreated() {
this.contextRunner.withUserConfiguration(
BasicConfiguration.class,
ConditionalOnBeanConfiguration.class
)
// ommitted for brevity
}
@Test
public void whenDependentBeanIsNotPresent_thenConditionalMissingBeanCreated() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingBeanConfiguration.class)
// ommitted for brevity
}

2.3. Состояние тестового объекта

В этом разделе давайте протестируем классы автоконфигурации, которые используют аннотации @ConditionalOnProperty .

Во-первых, нам нужно свойство для этого теста:

com.foreach.service=custom

После этого мы пишем вложенные классы автоконфигурации для создания bean-компонентов на основе предыдущего свойства:

@Configuration
@TestPropertySource("classpath:ConditionalOnPropertyTest.properties")
protected static class SimpleServiceConfiguration {
@Bean
@ConditionalOnProperty(name = "com.foreach.service", havingValue = "default")
@ConditionalOnMissingBean
public DefaultService defaultService() {
return new DefaultService();
}
@Bean
@ConditionalOnProperty(name = "com.foreach.service", havingValue = "custom")
@ConditionalOnMissingBean
public CustomService customService() {
return new CustomService();
}
}

Теперь мы вызываем метод withPropertyValues , чтобы переопределить значение свойства в каждом тесте:

@Test
public void whenGivenCustomPropertyValue_thenCustomServiceCreated() {
this.contextRunner.withPropertyValues("com.foreach.service=custom")
.withUserConfiguration(SimpleServiceConfiguration.class)
.run(context -> {
assertThat(context).hasBean("customService");
SimpleService simpleService = context.getBean(CustomService.class);
assertThat(simpleService.serve()).isEqualTo("Custom Service");
assertThat(context).doesNotHaveBean("defaultService");
});
}

@Test
public void whenGivenDefaultPropertyValue_thenDefaultServiceCreated() {
this.contextRunner.withPropertyValues("com.foreach.service=default")
.withUserConfiguration(SimpleServiceConfiguration.class)
.run(context -> {
assertThat(context).hasBean("defaultService");
SimpleService simpleService = context.getBean(DefaultService.class);
assertThat(simpleService.serve()).isEqualTo("Default Service");
assertThat(context).doesNotHaveBean("customService");
});
}

3. Заключение

Подводя итог, в этом руководстве показано , как использовать ApplicationContextRunner для запуска ApplicationContext с настройками и применения утверждений .

Здесь мы рассмотрели наиболее часто используемые сценарии вместо исчерпывающего списка того, как настроить ApplicationContext.

Тем временем имейте в виду, что ApplicationConetxtRunner предназначен для не-веб-приложений, поэтому рассмотрите WebApplicationContextRunner для веб-приложений на основе сервлетов и ReactiveWebApplicationContextRunner для реактивных веб-приложений.

Исходный код этого руководства можно найти на GitHub .