Spring Boot Testing 相对于 Spring Testing 而言,有两点显著的变化:
- 上下文环境变化。上下文环境由 ApplicationContext 变化成 SpringApplcation,因此专门新增 SpringBootContextLoader 加载 Spring Boot 的上下文环境 SpringApplcation。
自动装配测试环境。是的,测试类也需要自动装配。spring-boot-test-autoconfigure 工程就是专门自动加载测试环境的。
1. 核心原理
Spring Boot 的典型用法如下。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {
@Autowired
private ApplicationContext context;
}
1.1 @SpringBootTest
我们重点分析 @SpringBootTest 注解都做了那些事情。@SpringBootTest 是一个复合注解,可以指定配置类及外部属性等,最重要的扩展了 SpringBootTestContextBootstrapper。
需要特别说明的是,如果 @SpringBootTest 未指定 classes 配置类,默认会查找同包下 @SpringBootConfiguration(@SpringBootApplication) 标注的配置类,通常也就是我们应用的启动类,这会导致测试和应用采用相同的配置类,代码见 SpringBootTestContextBootstrapper.getOrFindConfigurationClasses。 这其实对测试并不好友好,因为通常我们需要将应用和测试的配置隔离,单独定义一个测试环境。@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
public @interface SpringBootTest {
String[] properties() default {};
Class<?>[] classes() default {};
WebEnvironment webEnvironment() default WebEnvironment.MOCK;
}
说明:在 spring-test 中,通过 TestContextBootstrapper 启动类加载 TestContext,进而加载 ApplicationContext。同样,spring-boot 也扩展了 SpringBootTestContextBootstrapper,委托 SpringBootContextLoader 加载 SpringApplication。
@Override
public ApplicationContext loadContext(MergedContextConfiguration config) throws Exception {
Class<?>[] configClasses = config.getClasses();
String[] configLocations = config.getLocations();
SpringApplication application = getSpringApplication();
application.setMainApplicationClass(config.getTestClass());
application.addPrimarySources(Arrays.asList(configClasses));
application.getSources().addAll(Arrays.asList(configLocations));
ConfigurableEnvironment environment = getEnvironment();
if (!ObjectUtils.isEmpty(config.getActiveProfiles())) {
setActiveProfiles(environment, config.getActiveProfiles());
}
ResourceLoader resourceLoader = (application.getResourceLoader() != null) ? application.getResourceLoader()
: new DefaultResourceLoader(getClass().getClassLoader());
TestPropertySourceUtils.addPropertiesFilesToEnvironment(environment, resourceLoader,
config.getPropertySourceLocations());
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, getInlinedProperties(config));
application.setEnvironment(environment);
List<ApplicationContextInitializer<?>> initializers = getInitializers(config, application);
...
application.setInitializers(initializers);
return application.run(getArgs(config));
}
1.2 ContextCustomizer
在 Spring Testing 中就提供了 ContextCustomizer 接口,用于在容器初始化前自定义 ApplicationContext。Spring Boot Testing 中会将 ContextCustomizer 适配成 ApplicationContextInitializer,然后设置给 SpringApplication。SpringApplication 启动前会调用 ApplicationContextInitializer 来调整 ApplicationContext 的配置。
@FunctionalInterface
public interface ContextCustomizer {
void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig);
}
在 Spring Boot 中大量使用 ContextCustomizer 来扩展功能,默认扩展如下:
# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.test.context.ImportsContextCustomizerFactory,\ // 注入testClass配置
org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory // 整合mockito
...
2. mockito
Spring Boot 官方文档《Spring Boot 26.3.9. Mocking and Spying Beans》。
Spring Boot 整合 mockito,提供了 @MockBean 和 @SpyBean 两个注解,@SpyBean 使用时没有 mock 的方法会调用原有的实现。@SpringBootTest
class MyTests {
@MockBean
private RemoteService remoteService;
}
相关的实现类:
- MockitoContextCustomizerFactory:对外提供的工厂类,创建 MockitoContextCustomizer,用于装配 MockitoPostProcessor。
- MockitoTestExecutionListener:处理测试类 mock 依赖注入,最终调用 MockitoPostProcessor.inject。
- MockitoPostProcessor:(核心)有两大功能,一是解析配置类的 mock Definition,调用 DefinitionsParser 解析注解信息,并使用 mock 的结果替换容器中原有的实现类定义。二是对配置类中的 mock 字段进行依赖注入。
- DefinitionsParser:调用 parse 方法解析 @MockBean 和 @SpyBean 注解;通过 getDefinitions 方法获取解析后的结果。 说明:Spring Boot 整合 mockito 大致可以分为三步:
- 解析测试类和配置类。MockitoContextCustomizer 组装 MockitoPostProcessor 后置处理器,并解析 testClass 类中的 @MockBean 和 @SpyBean 注解。MockitoPostProcessor 后置处理器实现了 BeanFactoryPostProcessor 接口,在容器启动时会继续解析配置类中的相关注解。
- 实例类mock注入。MockitoPostProcessor 同样实现了 SmartInstantiationAwareBeanPostProcessor 接口,在 Bean 实例化时会对 @MockBean 和 @SpyBean 标注的字段进行属性注入。
- 测试类mock注入。测试类并不属于 Spring 容器,因此只能通过 MockitoTestExecutionListener 监听器在测试类运行前进行属性注入。
注意事项:使用 @MockBean 时,本质是使用 Mockito.mock(Class) 替换了 Spring 容器中原有的 bean 对象,污染了原有的 Spring 容器,因此测试类中 @MockBean 时往往会重新启动一个新的 ApplicationContext。
// MergedContextConfiguration
@Override
public boolean equals(@Nullable Object other) {
...
// MockitoContextCustomizer 不同的测试类对应的definitions往往并不相同
if (!this.contextCustomizers.equals(otherConfig.contextCustomizers)) {
return false;
}
}
在 Spring 中,@``RunnerWith``(SpringRunner.class) class MyTest {}
直接启动容器,没有组装其它组件,启动的代价也不大。然而如果是 Spring Boot Testing 由于装配的组件大多,则代价比较大,启动比较耗时。
3. 自动装配
Spring Boot Testing 自动装配见官网《Test Auto-configuration Annotations》。
spring-boot-test-autoconfigure 工程提供了 @AutoConfigureJdbc、@JdbcTest、@AutoConfigureWebMvc 等专门用于装配测试组件的注解。这些注解都是 @ImportAutoConfiguration 的派生注解,通过 spring.factories 配置文件自动装配,其中 key 即为 @AutoConfigureJdbc 注解的全定限名。
@ImportAutoConfiguration
public @interface AutoConfigureWebMvc {
}
对应 spring.factories 配置如下:
# AutoConfigureWebMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
...
总结时刻
推荐阅读
- 《Test Auto-configuration Annotations》:Spring Boot 官网测试类自动装配。
- 《26. Spring Boot Testing》:Spring Boot 官网文档。
每天用心记录一点点。内容也许不重要,但习惯很重要!