1. 概述
Spring Boot 的自动配置(Auto-Configuration)是其核心特性之一,但要测试这些自动配置的场景并不容易。尤其是当你需要模拟不同类路径、Bean 或配置属性存在与否的情况时。
幸运的是,Spring Boot 提供了一个非常实用的测试工具类:ApplicationContextRunner。它可以帮助我们快速构建和测试一个定制的 ApplicationContext
,并通过类似 AssertJ 的链式断言方式来验证结果。
本文将通过几个典型场景,演示如何使用 ApplicationContextRunner
测试不同条件下的自动配置行为。
2. 测试自动配置场景
ApplicationContextRunner
是一个用于运行 ApplicationContext
并提供链式断言的工具类。推荐将其作为测试类的字段进行初始化,便于在多个测试方法中复用:
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.baeldung.autoconfiguration.ConditionalOnClassIntegrationTest")
protected static class ConditionalOnMissingClassConfiguration {
@Bean
public String missed() {
return "This is missed when ConditionalOnClassIntegrationTest is present on the classpath";
}
}
接下来使用 ApplicationContextRunner
测试这些配置是否按预期生效:
✅ 类存在时 Bean 被创建
@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");
});
}
❌ 类存在时 Bean 不应被创建
@Test
public void whenDependentClassIsPresent_thenBeanMissing() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)
.run(context -> {
assertThat(context).doesNotHaveBean("missed");
});
}
✅ 类不存在时 Bean 不应被创建
这里就要用到 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);
});
}
❌ 类不存在时 Bean 应该被创建
@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 是否存在(@ConditionalOnBean / @ConditionalOnMissingBean)
这两个注解用于根据上下文中是否存在某个 Bean 来决定是否创建新 Bean。
我们定义如下配置类:
@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";
}
}
然后测试这些配置:
✅ 依赖 Bean 存在时,目标 Bean 被创建
@Test
public void whenDependentBeanIsPresent_thenConditionalBeanCreated() {
this.contextRunner.withUserConfiguration(
BasicConfiguration.class,
ConditionalOnBeanConfiguration.class
).run(context -> {
assertThat(context).hasBean("createOnBean");
assertThat(context.getBean("createOnBean")).isEqualTo("This is created when bean (name=created) is present");
});
}
✅ 依赖 Bean 不存在时,目标 Bean 被创建
@Test
public void whenDependentBeanIsNotPresent_thenConditionalMissingBeanCreated() {
this.contextRunner.withUserConfiguration(ConditionalOnMissingBeanConfiguration.class)
.run(context -> {
assertThat(context).hasBean("createOnMissingBean");
assertThat(context.getBean("createOnMissingBean")).isEqualTo("This is created when bean (name=created) is missing");
});
}
2.3. 测试属性值(@ConditionalOnProperty)
该注解用于根据配置属性值决定是否启用某个 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();
}
}
然后使用 withPropertyValues
方法模拟不同的配置值:
✅ 配置值为 custom 时,CustomService 被创建
@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");
});
}
✅ 配置值为 default 时,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
简单粗暴地测试 Spring Boot 自动配置的常见场景:
- 类是否存在(@ConditionalOnClass / @ConditionalOnMissingClass)
- Bean 是否存在(@ConditionalOnBean / @ConditionalOnMissingBean)
- 属性值是否匹配(@ConditionalOnProperty)
⚠️ 注意:ApplicationContextRunner
适用于非 Web 应用的测试。如果是基于 Servlet 的 Web 应用,应使用 WebApplicationContextRunner
;如果是响应式 Web 应用,则应使用 ReactiveWebApplicationContextRunner
。
完整的示例代码可在 GitHub 上找到。