1. 概述
眾所周知,自動配置是 Spring Boot 的關鍵特性之一,但測試自動配置場景可能會比較困難。
在後續部分,我們將展示如何使用 <em >ApplicationContextRunner</em > 簡化自動配置測試。
2. 自主配置測試場景
ApplicationContextRunner 是一個工具類,它運行 ApplicationContext 並提供 AssertJ 風格的斷言。 建議將其作為測試類中的字段使用,以便進行共享配置,並在每個測試後進行自定義:
private final ApplicationContextRunner contextRunner
= new ApplicationContextRunner();
讓我們繼續演示它的強大功能,通過測試幾個案例來驗證。
2.1 測試類條件
本節將測試一些使用 <em/>@ConditionalOnClass</em/> 和 <em/>@ConditionalOnMissingClass</em/> 註解的自動配置類。
@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.baeldung.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. 測試 Bean 條件
我們剛才研究了 @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";
}
}
然後,我們調用 <em withUserConfiguration 方法,就像前一節所述,並將我們的自定義配置類發送過去,以測試自動配置是否在不同條件下恰當地實例化或跳過 <em createOnBean 或 <em createOnMissingBean 類型的 Bean:
@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.baeldung.service=custom之後,我們編寫嵌套的自動配置類,以基於先前定義的屬性創建 Bean。
@Configuration
@TestPropertySource("classpath:ConditionalOnPropertyTest.properties")
protected static class SimpleServiceConfiguration {
@Bean
@ConditionalOnProperty(name = "com.baeldung.service", havingValue = "default")
@ConditionalOnMissingBean
public DefaultService defaultService() {
return new DefaultService();
} @Bean
@ConditionalOnProperty(name = "com.baeldung.service", havingValue = "custom")
@ConditionalOnMissingBean
public CustomService customService() {
return new CustomService();
}
}
現在,我們調用 <em withPropertyValues 方法來覆蓋每個測試中的屬性值:
@Test
public void whenGivenCustomPropertyValue_thenCustomServiceCreated() {
this.contextRunner.withPropertyValues("com.baeldung.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.baeldung.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 僅適用於非 Web 應用程序,因此對於基於 Servlet 的 Web 應用程序,請考慮使用 WebApplicationContextRunner,對於響應式 Web 應用程序,請使用 ReactiveWebApplicationContextRunner。