测试自动配置与 @SpringBootTest 引导细节
概述
Spring Boot 的测试支持通过 @SpringBootTest 和一系列切片注解提供了灵活的集成测试和单元测试能力。测试自动配置的核心包括 SpringBootTestContextBootstrapper 的自定义引导策略、切片测试的 TypeExcludeFilter 隔离机制、@MockBean/@SpyBean 的 Mock 注入、TestPropertyValues 动态属性源等。
本文将深入拆解 Spring Boot 测试自动配置与 @SpringBootTest 引导的 10 个关键细节,涵盖测试引导策略、切片测试过滤、Mock 注入机制、测试属性配置、输出捕获、MockMvc 自动配置等核心内容。
本文基于 Spring Boot 3.2.5 源码分析。
1. @SpringBootTest 的 @BootstrapWith(SpringBootTestContextBootstrapper.class)
@SpringBootTest 通过 @BootstrapWith 指定自定义的测试上下文引导策略。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@BootstrapWith(SpringBootTestContextBootstrapper.class) // 自定义引导器
@ExtendWith(SpringExtension.class) // JUnit 5 扩展
public @interface SpringBootTest {
// 指定 Web 环境类型
@AliasFor("webEnvironment")
WebEnvironment value() default WebEnvironment.MOCK;
// 指定 Web 环境类型
WebEnvironment webEnvironment() default WebEnvironment.MOCK;
// 指定启动类(不指定时自动查找)
@AliasFor(annotation = SpringBootConfiguration.class, attribute = "value")
Class<?>[] classes() default {};
// 指定启动参数
String[] args() default {};
// 指定属性(比配置文件优先级高)
String[] properties() default {};
enum WebEnvironment {
MOCK, // 加载 WebApplicationContext,但使用 Mock 的 Servlet 环境
RANDOM_PORT, // 启动内嵌容器,随机端口
DEFINED_PORT, // 启动内嵌容器,使用配置端口
NONE // 不加载 WebApplicationContext
}
}引导过程:
// SpringExtension(JUnit 5)中触发引导
public class SpringExtension implements BeforeAllCallback, ... {
@Override
public void beforeAll(ExtensionContext context) {
// 获取 TestContextManager → 触发引导
getTestContextManager(context).beforeTestClass();
}
}
// TestContextManager 中调用 BootstrapWith
// → SpringBootTestContextBootstrapper 的 bootstrapTestContext()
// TestContextBootstrapper 接口
public interface TestContextBootstrapper {
// 构建测试上下文
TestContext buildTestContext();
// 构建 MergedContextConfiguration
MergedContextConfiguration buildMergedContextConfiguration();
}与默认引导器的对比:
| 特性 | DefaultTestContextBootstrapper | SpringBootTestContextBootstrapper |
|---|---|---|
| 启动类查找 | 从 @ContextConfiguration 指定 | 自动查找 @SpringBootApplication 类 |
| 属性源加载 | @TestPropertySource | @TestPropertySource + properties 属性 |
| ContextCustomizer | 不支持 | 支持(如 SpringBootWebTestClientContextCustomizer) |
@ActiveProfiles | 支持 | 支持 |
2. SpringBootTestContextBootstrapper 的 3 个核心方法
SpringBootTestContextBootstrapper 是 @SpringBootTest 引导的核心类,负责查找启动类、合并配置和构建上下文。
public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstrapper {
// 核心方法 1: 查找主配置类(@SpringBootApplication 类)
@Override
protected Class<?> findMainConfigurationClass(Class<?> testClass) {
// 1. 从 @SpringBootTest(classes = ...) 中获取
SpringBootTest annotation = AnnotatedElementUtils
.findMergedAnnotation(testClass, SpringBootTest.class);
if (annotation != null && annotation.classes().length > 0) {
return annotation.classes()[0]; // 返回显式指定的类
}
// 2. 从调用栈中查找 @SpringBootApplication 类
Class<?> mainClass = deduceMainApplicationClass();
if (mainClass != null) {
return mainClass;
}
// 3. 从测试类所在包扫描查找
throw new IllegalStateException("无法找到 @SpringBootApplication 类。"
+ "请通过 @SpringBootTest(classes = YourApplication.class) 指定");
}
// 核心方法 2: 查找测试属性
private List<TestPropertySource> findProperties(Class<?> testClass) {
List<TestPropertySource> propertySources = new ArrayList<>();
// 1. 收集 @TestPropertySource 注解(来自测试类层级)
propertySources.addAll(super.findProperties(testClass));
// 2. 处理 @SpringBootTest(properties = "...") 中的属性
SpringBootTest annotation = AnnotatedElementUtils
.findMergedAnnotation(testClass, SpringBootTest.class);
if (annotation != null && annotation.properties().length > 0) {
propertySources.add(createInlineTestPropertySource(annotation.properties()));
}
return propertySources;
}
// 核心方法 3: 构建合并的上下文配置
@Override
public MergedContextConfiguration buildMergedContextConfiguration() {
Class<?> testClass = getTestClass();
// 1. 查找主配置类
Class<?> mainClass = findMainConfigurationClass(testClass);
// 2. 构建配置类列表(主配置类 + 显式配置类)
Set<Class<?>> configClasses = new LinkedHashSet<>();
configClasses.add(mainClass);
addExplicitConfigurationClasses(testClass, configClasses);
// 3. 加载 ContextCustomizer(如 WebTestClient、MockMvc 等)
Set<ContextCustomizer> contextCustomizers = new LinkedHashSet<>();
contextCustomizers.addAll(getContextCustomizers(testClass));
// 4. 构建 MergedContextConfiguration
return new MergedContextConfiguration(testClass,
configClasses.toArray(new Class<?>[0]),
mergedContextConfig.getActiveProfiles(),
mergedContextConfig.getPropertySourceDescriptors(),
mergedContextConfig.getContextInitializerClasses(),
contextCustomizers.toArray(new ContextCustomizer[0]),
mergedContextConfig.getTestExecutionListeners(),
mergedContextConfig.getContextCustomizerFactoryContext());
}
}3 个核心方法的协作流程:
@SpringBootTest 标注的测试类
↓
buildMergedContextConfiguration()
↓
① findMainConfigurationClass()
→ 猜测或查找 @SpringBootApplication 启动类
↓
② findProperties()
→ 收集 @TestPropertySource + properties 属性
↓
③ 整合配置类列表、ContextCustomizer、MergedContextConfiguration
↓
TestContextManager 使用 MergedContextConfiguration 创建 ApplicationContext3. @WebMvcTest(MyController.class) 的 TypeExcludeFilter 切片
切片测试通过 TypeExcludeFilter 和 @AutoConfigureXXX 机制限制只加载特定类型的 Bean。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@AutoConfigureMockMvc // 自动配置 MockMvc
@AutoConfigureWebMvc // 自动配置 Web MVC
@TypeExcludeFilter(WebMvcTypeExcludeFilter.class) // 排除过滤器
public @interface WebMvcTest {
// 指定要测试的 Controller
Class<?>[] value() default {};
}
// WebMvcTypeExcludeFilter
class WebMvcTypeExcludeFilter extends StandardAnnotationCustomizableTypeExcludeFilter<WebMvcTest> {
@Override
protected void addExcludes(Set<Class<?>> excludes) {
// 2. 排除非 Controller 类
excludes.add(ControllerAdvice.class);
excludes.add(Converter.class);
excludes.add(ConverterFactory.class);
excludes.add(GenericConverter.class);
excludes.add(WebMvcConfigurer.class);
excludes.add(HttpMessageConverter.class);
// ... 其他非测试相关的类
}
@Override
protected boolean isExcludeComponent(Class<?> componentClass) {
// 1. 排除没有 @Controller / @ControllerAdvice 注解的 Bean
if (!isController(componentClass)) {
return true;
}
return false;
}
private boolean isController(Class<?> componentClass) {
// 只保留 @Controller 类(含 @RestController)
return AnnotatedElementUtils.hasAnnotation(componentClass, Controller.class);
}
}TypeExcludeFilter 的工作机制:
// TypeExcludeFilter — 在 BeanDefinition 扫描阶段排除不需要的类
public class TypeExcludeFilter implements TypeFilter, AotAware {
private final List<TypeFilter> delegates = new ArrayList<>();
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// 遍历所有委托过滤器
for (TypeFilter delegate : this.delegates) {
if (delegate.match(metadataReader, metadataReaderFactory)) {
// 只要任意一个过滤器匹配 → 排除该 BeanDefinition
return true;
}
}
return false;
}
}各种切片测试的过滤范围:
| 切片注解 | 保留的 Bean | 排除的 Bean |
|---|---|---|
@WebMvcTest | @Controller、@ControllerAdvice、Filter、WebMvcConfigurer | @Service、@Repository、@Component |
@DataJpaTest | @Repository、EntityManager、DataSource | @Service、@Controller、@Component |
@JsonTest | ObjectMapper、@JsonComponent | 所有非 JSON 相关 Bean |
@RestClientTest | RestTemplate、MockRestServiceServer | 其他所有 Bean |
4. 切片测试的自动配置过滤
切片测试通过 @AutoConfigureXXX 和 ImportAutoConfiguration 限定自动配置的范围。
// @DataJpaTest 的自动配置限定
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@DataJpaTest
@AutoConfigureDataJpa // 仅启用 JPA 相关配置
@AutoConfigureTestDatabase // 使用嵌入式数据库替换
@AutoConfigureCache // 禁用缓存
@TypeExcludeFilter(DataJpaTypeExcludeFilter.class) // 排除非 Repository Bean
public @interface DataJpaTest {
// 是否启用自动配置的 SQL 初始化
boolean useDefaultFilters() default true;
// 是否包含过滤器
Filter[] includeFilters() default {};
// 是否排除过滤器
Filter[] excludeFilters() default {};
}
// @DataJpaTest 加载的自动配置
@ImportAutoConfiguration
@AutoConfigureDataJpa
public @interface DataJpaTest { }切片测试的自动配置限制策略:
// @AutoConfigureDataJpa 的定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ImportAutoConfiguration
public @interface AutoConfigureDataJpa {
// 这个注解内部通过 @ImportAutoConfiguration 限定配置范围
}
// Spring Boot 在 DataJpaTest 自动配置时只加载:
// - DataSourceAutoConfiguration
// - HibernateJpaAutoConfiguration
// - JpaRepositoriesAutoConfiguration
// - TransactionAutoConfiguration
// - TestDatabaseAutoConfiguration
// 不加载的配置(与 Web 相关):
// - WebMvcAutoConfiguration
// - JacksonAutoConfiguration(除非手动添加)
// - SecurityAutoConfiguration@AutoConfigureTestDatabase 替换数据源:
@AutoConfigureTestDatabase
public @interface DataJpaTest {
// 自动将 DataSource 替换为嵌入式数据库(H2 / HSQLDB / Derby)
}
@AutoConfigureTestDatabase(replace = Replace.ANY) // 替换所有 DataSource(默认)
@AutoConfigureTestDatabase(replace = Replace.NONE) // 不替换(使用实际数据库)
@AutoConfigureTestDatabase(replace = Replace.AUTO_CONFIGURED) // 仅替换自动配置的5. @MockBean 注入机制
@MockBean 和 @SpyBean 通过 MockitoPostProcessor 在 BeanFactory 后置处理阶段创建 Mock 并注册到容器。
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MockBean {
// Mock 的类
Class<?>[] value() default {};
// Mock 的名称
String[] name() default {};
// Mock 的其他配置
MockReset reset() default MockReset.MOCK_RESET_NONE;
}MockitoPostProcessor 的核心实现:
public class MockitoPostProcessor implements BeanFactoryPostProcessor, BeanPostProcessor {
// 存储需要 Mock 的 Bean 定义
private final Map<Field, MockDefinition> mockFields = new HashMap<>();
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 1. 收集测试类中 @MockBean / @SpyBean 标注的字段
// 为每个字段创建 MockDefinition
// 2. 对于不需要被代理的 Mock,直接在 BeanFactory 中注册单例
for (MockDefinition definition : this.mockDefinitions.values()) {
if (!definition.needsProxy()) {
// 创建 Mock 实例
Object mock = definition.createMock();
// 注册到 BeanFactory
beanFactory.registerSingleton(definition.getBeanName(), mock);
} else {
// 需要代理 → 在后面 BeanPostProcessor 阶段处理
}
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 处理需要代理的 Mock(替换容器中已存在的 Bean)
for (MockDefinition definition : this.mockDefinitions.values()) {
if (definition.needsProxy()
&& beanName.equals(definition.getOriginalBeanName())) {
// 用 Mock 实例替换原始 Bean
return definition.createMock();
}
}
return bean;
}
}@MockBean vs @SpyBean:
// @MockBean — 创建完整的 Mock 对象,所有方法返回默认值
@MockBean
private UserService userService;
// @SpyBean — 创建 Spy 对象,保留真实行为
@SpyBean
private UserService userService;
// 使用对比
@SpyBean
private UserService userService;
@Test
void testFindUser() {
// MockBean:默认返回 null
// SpyBean:调用真实 userService.findUser()
// 模拟特定行为
given(userService.findUser(1L)).willReturn(new User("张三"));
// SpyBean 仍然保留其他方法的真实行为
}@MockBean 对 BeanFactory 的影响:
有 @MockBean 标注的测试:
↓
postProcessBeanFactory()
↓
① 创建 Mock 对象(使用 Mockito.mock())
↓
② 将 Mock 注册为单例 Bean,覆盖容器中的原始 Bean
↓
③ BeanPostProcessor.postProcessAfterInitialization()
↓
如果容器原来已有同类型 Bean,拦截初始化并替换为 Mock
↓
测试方法中 @MockBean 字段自动注入 Mock 实例6. MockitoTestExecutionListener 的 Mock 初始化
MockitoTestExecutionListener 负责在测试执行前后管理 Mockito Mock 的状态。
public class MockitoTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void beforeTestMethod(TestContext testContext) {
// 在测试方法执行前初始化 @Mock 和 @InjectMocks 字段
if (Mockito.mockingDetails(testContext.getTestInstance()).isMock()) {
// 如果测试实例本身是 Mock,跳过
return;
}
// 1. 初始化 @Mock 字段(创建 Mock 实例并注入)
MockitoAnnotations.openMocks(testContext.getTestInstance());
// 2. 处理 @InjectMocks 自动注入
// 将 @Mock 创建的 Mock 实例自动注入到 @InjectMocks 的目标对象中
}
@Override
public void afterTestMethod(TestContext testContext) {
// 在测试方法执行后重置 Mock 状态
Object testInstance = testContext.getTestInstance();
if (testInstance != null) {
// 查找所有 @MockBean 和 @SpyBean 字段
List<Field> mockFields = FieldUtils.getFieldsWithAnnotation(
testInstance.getClass(), MockBean.class);
mockFields.addAll(FieldUtils.getFieldsWithAnnotation(
testInstance.getClass(), SpyBean.class));
// 重置每个 Mock(清空 when() 设置)
for (Field field : mockFields) {
Object mock = FieldUtils.readField(field, testInstance, true);
Mockito.reset(mock);
}
}
}
}测试执行生命周期的监听器链:
beforeTestClass()
└── 初始化 TestContextManager, 准备 ApplicationContext
↓
beforeTestMethod()
├── MockitoTestExecutionListener.beforeTestMethod()
│ ├── 初始化 @Mock / @InjectMocks 字段
│ └── 准备 Mock 对象
└── 其他 TestExecutionListener...
↓
testMethod() ← 执行测试方法
↓
afterTestMethod()
├── MockitoTestExecutionListener.afterTestMethod()
│ └── 重置所有 @MockBean / @SpyBean 状态
└── 其他 TestExecutionListener...
↓
afterTestClass()
└── 关闭 ApplicationContext7. TestPropertyValues.of("server.port=8081").applyTo(context)
TestPropertyValues 提供了一种在测试中动态追加 PropertySource 的方式。
public final class TestPropertyValues {
private final List<String> pairs; // "key=value" 列表
private TestPropertyValues(List<String> pairs) {
this.pairs = pairs;
}
// 创建 TestPropertyValues 实例
public static TestPropertyValues of(String... pairs) {
return new TestPropertyValues(Arrays.asList(pairs));
}
public static TestPropertyValues of(Map<String, String> map) {
return new TestPropertyValues(map.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.toList()));
}
// 应用到 ConfigurableEnvironment
public void applyTo(ConfigurableEnvironment environment, Type type, String name) {
// 创建包含所有键值对的 MapPropertySource
Properties properties = new Properties();
for (String pair : this.pairs) {
int index = pair.indexOf('=');
String key = pair.substring(0, index);
String value = pair.substring(index + 1);
properties.put(key, value);
}
MapPropertySource propertySource = new MapPropertySource(name, properties);
// 根据 Type 决定添加位置
switch (type) {
case PRIORITY:
// 添加到最前面(最高优先级)
environment.getPropertySources().addFirst(propertySource);
break;
case SYSTEM:
// 添加到系统属性之后,应用配置之前
environment.getPropertySources()
.addAfter("systemProperties", propertySource);
break;
case APPLICATION:
// 添加到应用配置之后
environment.getPropertySources()
.addLast(propertySource);
break;
}
}
}使用场景:
@SpringBootTest
class UserServiceTest {
@Test
void testWithCustomProperties(ConfigurableEnvironment environment) {
// 测试前动态修改配置
TestPropertyValues.of(
"server.port=8081",
"spring.datasource.url=jdbc:h2:mem:testdb",
"app.feature.enabled=true"
).applyTo(environment);
// 此时 Environment 中的属性已经更新
assertEquals("8081", environment.getProperty("server.port"));
}
}@SpringBootTest 中的等效配置:
@SpringBootTest(properties = {
"server.port=8081",
"spring.datasource.url=jdbc:h2:mem:testdb"
})
class UserServiceTest {
// 这两种方式等效
// 但 TestPropertyValues.applyTo() 可以在测试方法中动态修改
}8. OutputCapture 捕获日志/控制台输出
OutputCapture 是 Spring Boot 提供的测试辅助类,用于捕获 System.out 和 System.err 的输出。
public class OutputCapture implements TestExecutionListener {
private CaptureOutputStream captureOut;
private CaptureOutputStream captureErr;
private ByteArrayOutputStream copy;
@Override
public void beforeTestMethod(TestContext testContext) {
// 创建用于捕获输出的字节数组流
this.copy = new ByteArrayOutputStream();
// 代理 System.out
this.captureOut = new CaptureOutputStream(System.out, this.copy);
System.setOut(new PrintStream(this.captureOut));
// 代理 System.err
this.captureErr = new CaptureOutputStream(System.err, this.copy);
System.setErr(new PrintStream(this.captureErr));
}
@Override
public void afterTestMethod(TestContext testContext) {
// 恢复原始 System.out 和 System.err
System.setOut(this.captureOut.getOriginal());
System.setErr(this.captureErr.getOriginal());
// 清理捕获的输出
this.copy = null;
}
// 返回捕获的所有输出
public String getAll() {
return this.copy != null ? this.copy.toString() : "";
}
// 检查输出是否包含特定文本
public void expect(Matcher<String> matcher) {
assertThat(getAll(), matcher);
}
// 内部类:代理输出流
private static class CaptureOutputStream extends FilterOutputStream {
private final PrintStream original;
private final ByteArrayOutputStream copy;
@Override
public void write(int b) throws IOException {
// 同时写入原始流和副本
this.original.write(b); // 依然输出到控制台
this.copy.write(b); // 写入副本以便断言
}
}
}使用示例:
@SpringBootTest
@ExtendWith(OutputCaptureExtension.class)
class LoggingServiceTest {
@Test
void testLogOutput(CapturedOutput output) {
// 测试方法中触发了日志输出
loggingService.doSomething();
// 断言输出内容
assertThat(output).contains("执行成功");
assertThat(output).doesNotContain("ERROR");
}
}OutputCapture vs CapturedOutput:
| 方式 | 使用方式 | 说明 |
|---|---|---|
@ExtendWith(OutputCaptureExtension.class) + CapturedOutput | JUnit 5 参数注入 | 推荐方式 |
@Rule OutputCapture outputCapture | JUnit 4 @Rule | 旧版兼容 |
System.setOut() + 手动代理 | 手动方式 | 不推荐 |
9. @AutoConfigureMockMvc 注册 MockMvc
@AutoConfigureMockMvc 自动创建 MockMvc 实例,用于 Web 层的集成测试。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ImportAutoConfiguration
@AutoConfigureWebMvc
public @interface AutoConfigureMockMvc {
// 是否将 MockMvc 添加到 Spring 测试上下文
boolean addFilters() default true;
// 是否打印请求详情
boolean printOnlyOnFailure() default true;
// 是否始终打印请求详情
boolean alwaysPrint() default false;
}MockMvcAutoConfiguration 的实现:
@AutoConfiguration(after = WebMvcAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(MockMvc.class)
public class MockMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MockMvc.class)
public MockMvc mockMvc(WebApplicationContext webApplicationContext,
ObjectProvider<MockMvcBuilderCustomizer> customizers) {
// 1. 创建 MockMvc 构建器
WebAppContextSetup setup = MockMvcBuilders.webAppContextSetup(webApplicationContext);
// 2. 应用自定义器
customizers.orderedStream().forEach((customizer) -> customizer.customize(setup));
// 3. 应用 @AutoConfigureMockMvc 配置
if (this.printOnlyOnFailure) {
setup.alwaysDo(resultHandler -> { /* 仅失败时打印 */ });
}
if (this.addFilters) {
setup.addFilters(this.filters); // 添加过滤器
}
// 4. 构建 MockMvc
return setup.build();
}
}使用示例:
// @WebMvcTest 已包含 @AutoConfigureMockMvc,所以直接可用
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testGetUser() throws Exception {
mockMvc.perform(get("/users/1")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("张三"));
}
}MockMvc vs TestRestTemplate vs WebTestClient:
| 特性 | MockMvc | TestRestTemplate | WebTestClient |
|---|---|---|---|
| 加载方式 | @AutoConfigureMockMvc | @SpringBootTest(webEnvironment=RANDOM_PORT) | @AutoConfigureWebTestClient |
| 内嵌容器 | 不启动(Mock Servlet 环境) | 启动真实容器 | 可选(Mock 或真实) |
| 测试范围 | Controller 层 | 完整 HTTP 请求/响应 | 完整 HTTP 请求/响应 |
| 支持异步 | ❌ | ✅ | ✅ |
| 支持 WebFlux | ❌ | ❌ | ✅ |
10. SpringBootTestArgProvider 提供 SpringApplication 参数
SpringBootTestArgProvider 负责将 @SpringBootTest(args = {...}) 中的参数传递给 SpringApplication。
class SpringBootTestArgProvider implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final String[] args;
SpringBootTestArgProvider(String[] args) {
this.args = args;
}
@Override
public void initialize(ConfigurableApplicationContext context) {
// 将测试参数添加到 PropertySource 中
if (this.args.length > 0) {
// 将 args 转为 PropertySource(以 --key=value 格式)
TestPropertyValues.of(parseArgs(this.args))
.applyTo(context.getEnvironment());
}
}
private String[] parseArgs(String[] args) {
// 将 --key=value 格式的参数转换为 key=value
return Arrays.stream(args)
.filter(arg -> arg.startsWith("--"))
.map(arg -> arg.substring(2))
.toArray(String[]::new);
}
}SpringBootTestContextBootstrapper 中的整合:
public class SpringBootTestContextBootstrapper {
@Override
protected Set<Class<?>> getContextInitializerClasses(Class<?> testClass) {
Set<Class<?>> initializerClasses = super.getContextInitializerClasses(testClass);
// 获取 @SpringBootTest(args = ...)
SpringBootTest annotation = AnnotatedElementUtils
.findMergedAnnotation(testClass, SpringBootTest.class);
if (annotation != null && annotation.args().length > 0) {
// 注册 SpringBootTestArgProvider 作为 Initializer
initializerClasses.add(SpringBootTestArgProvider.class);
}
return initializerClasses;
}
}使用示例:
@SpringBootTest(args = {
"--server.port=8081",
"--spring.profiles.active=test"
})
class ApplicationTest {
@Autowired
private Environment env;
@Test
void testArgs() {
// @SpringBootTest(args = ...) 中的参数优先级最高
assertThat(env.getProperty("server.port")).isEqualTo("8081");
assertThat(env.getProperty("spring.profiles.active")).isEqualTo("test");
}
}参数优先级:
测试参数(@SpringBootTest(args = ...)) ← 最高优先级
↓
@SpringBootTest(properties = ...)
↓
@TestPropertySource
↓
application-test.properties
↓
application.yml
↓
系统属性(systemProperties)
↓
环境变量(systemEnvironment) ← 最低优先级总结
测试自动配置与 @SpringBootTest 引导的 10 个细节点总结如下:
| # | 细节点 | 核心类/机制 |
|---|---|---|
| ① | @SpringBootTest 的 @BootstrapWith(SpringBootTestContextBootstrapper.class) | 自定义测试上下文引导策略,替代 DefaultTestContextBootstrapper |
| ② | SpringBootTestContextBootstrapper 的 3 个核心方法 | findMainConfigurationClass() 查找启动类 → findProperties() 收集属性 → buildMergedContextConfiguration() 整合配置 |
| ③ | @WebMvcTest(MyController.class) 的 TypeExcludeFilter 切片 | WebMvcTypeExcludeFilter 排除非 @Controller / @ControllerAdvice 的 Bean |
| ④ | 切片测试的自动配置过滤 | @AutoConfigureDataJpa + @AutoConfigureTestDatabase + @TypeExcludeFilter 限定配置范围 |
| ⑤ | @MockBean 注入机制 | MockitoPostProcessor.postProcessBeanFactory() → 创建 Mock → 注册单例 → 替换原始 Bean |
| ⑥ | MockitoTestExecutionListener 的 Mock 初始化 | @Before 阶段 MockitoAnnotations.openMocks() → @After 阶段 Mockito.reset() |
| ⑦ | TestPropertyValues.of("key=val").applyTo(env) | 动态创建 MapPropertySource 并添加到 Environment 的指定位置 |
| ⑧ | OutputCapture 捕获日志/控制台输出 | System.setOut(captureStream) → 写入副本 → getAll() 获取输出 |
| ⑨ | @AutoConfigureMockMvc 注册 MockMvc | MockMvcBuilders.webAppContextSetup(context).build() |
| ⑩ | SpringBootTestArgProvider 提供参数 | @SpringBootTest(args = {"--key=val"}) → ApplicationContextInitializer 注册 |