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 .