Spring Boot Testing 相对于 Spring Testing 而言,有两点显著的变化:

  1. 上下文环境变化。上下文环境由 ApplicationContext 变化成 SpringApplcation,因此专门新增 SpringBootContextLoader 加载 Spring Boot 的上下文环境 SpringApplcation。
  2. 自动装配测试环境。是的,测试类也需要自动装配。spring-boot-test-autoconfigure 工程就是专门自动加载测试环境的。

    1. 核心原理

    Spring Boot 的典型用法如下。

    1. @RunWith(SpringRunner.class)
    2. @SpringBootTest
    3. public class ApplicationTest {
    4. @Autowired
    5. private ApplicationContext context;
    6. }

    1.1 @SpringBootTest

    我们重点分析 @SpringBootTest 注解都做了那些事情。@SpringBootTest 是一个复合注解,可以指定配置类及外部属性等,最重要的扩展了 SpringBootTestContextBootstrapper。
    需要特别说明的是,如果 @SpringBootTest 未指定 classes 配置类,默认会查找同包下 @SpringBootConfiguration(@SpringBootApplication) 标注的配置类,通常也就是我们应用的启动类,这会导致测试和应用采用相同的配置类,代码见 SpringBootTestContextBootstrapper.getOrFindConfigurationClasses。 这其实对测试并不好友好,因为通常我们需要将应用和测试的配置隔离,单独定义一个测试环境。

    1. @BootstrapWith(SpringBootTestContextBootstrapper.class)
    2. @ExtendWith(SpringExtension.class)
    3. public @interface SpringBootTest {
    4. String[] properties() default {};
    5. Class<?>[] classes() default {};
    6. WebEnvironment webEnvironment() default WebEnvironment.MOCK;
    7. }

    说明:在 spring-test 中,通过 TestContextBootstrapper 启动类加载 TestContext,进而加载 ApplicationContext。同样,spring-boot 也扩展了 SpringBootTestContextBootstrapper,委托 SpringBootContextLoader 加载 SpringApplication。

    1. @Override
    2. public ApplicationContext loadContext(MergedContextConfiguration config) throws Exception {
    3. Class<?>[] configClasses = config.getClasses();
    4. String[] configLocations = config.getLocations();
    5. SpringApplication application = getSpringApplication();
    6. application.setMainApplicationClass(config.getTestClass());
    7. application.addPrimarySources(Arrays.asList(configClasses));
    8. application.getSources().addAll(Arrays.asList(configLocations));
    9. ConfigurableEnvironment environment = getEnvironment();
    10. if (!ObjectUtils.isEmpty(config.getActiveProfiles())) {
    11. setActiveProfiles(environment, config.getActiveProfiles());
    12. }
    13. ResourceLoader resourceLoader = (application.getResourceLoader() != null) ? application.getResourceLoader()
    14. : new DefaultResourceLoader(getClass().getClassLoader());
    15. TestPropertySourceUtils.addPropertiesFilesToEnvironment(environment, resourceLoader,
    16. config.getPropertySourceLocations());
    17. TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, getInlinedProperties(config));
    18. application.setEnvironment(environment);
    19. List<ApplicationContextInitializer<?>> initializers = getInitializers(config, application);
    20. ...
    21. application.setInitializers(initializers);
    22. return application.run(getArgs(config));
    23. }

    1.2 ContextCustomizer

    在 Spring Testing 中就提供了 ContextCustomizer 接口,用于在容器初始化前自定义 ApplicationContext。Spring Boot Testing 中会将 ContextCustomizer 适配成 ApplicationContextInitializer,然后设置给 SpringApplication。SpringApplication 启动前会调用 ApplicationContextInitializer 来调整 ApplicationContext 的配置。

    1. @FunctionalInterface
    2. public interface ContextCustomizer {
    3. void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig);
    4. }

    在 Spring Boot 中大量使用 ContextCustomizer 来扩展功能,默认扩展如下:

    1. # Spring Test ContextCustomizerFactories
    2. org.springframework.test.context.ContextCustomizerFactory=\
    3. org.springframework.boot.test.context.ImportsContextCustomizerFactory,\ // 注入testClass配置
    4. org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\
    5. org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory // 整合mockito
    6. ...

    2. mockito

    Spring Boot 官方文档《Spring Boot 26.3.9. Mocking and Spying Beans》。
    Spring Boot 整合 mockito,提供了 @MockBean 和 @SpyBean 两个注解,@SpyBean 使用时没有 mock 的方法会调用原有的实现。

    1. @SpringBootTest
    2. class MyTests {
    3. @MockBean
    4. private RemoteService remoteService;
    5. }

    相关的实现类:

  • MockitoContextCustomizerFactory:对外提供的工厂类,创建 MockitoContextCustomizer,用于装配 MockitoPostProcessor。
  • MockitoTestExecutionListener:处理测试类 mock 依赖注入,最终调用 MockitoPostProcessor.inject
  • MockitoPostProcessor:(核心)有两大功能,一是解析配置类的 mock Definition,调用 DefinitionsParser 解析注解信息,并使用 mock 的结果替换容器中原有的实现类定义。二是对配置类中的 mock 字段进行依赖注入。
  • DefinitionsParser:调用 parse 方法解析 @MockBean 和 @SpyBean 注解;通过 getDefinitions 方法获取解析后的结果。 Spring Boot Testing - 图1说明:Spring Boot 整合 mockito 大致可以分为三步:
  1. 解析测试类和配置类。MockitoContextCustomizer 组装 MockitoPostProcessor 后置处理器,并解析 testClass 类中的 @MockBean 和 @SpyBean 注解。MockitoPostProcessor 后置处理器实现了 BeanFactoryPostProcessor 接口,在容器启动时会继续解析配置类中的相关注解。
  2. 实例类mock注入。MockitoPostProcessor 同样实现了 SmartInstantiationAwareBeanPostProcessor 接口,在 Bean 实例化时会对 @MockBean 和 @SpyBean 标注的字段进行属性注入。
  3. 测试类mock注入。测试类并不属于 Spring 容器,因此只能通过 MockitoTestExecutionListener 监听器在测试类运行前进行属性注入。

注意事项:使用 @MockBean 时,本质是使用 Mockito.mock(Class) 替换了 Spring 容器中原有的 bean 对象,污染了原有的 Spring 容器,因此测试类中 @MockBean 时往往会重新启动一个新的 ApplicationContext。

  1. // MergedContextConfiguration
  2. @Override
  3. public boolean equals(@Nullable Object other) {
  4. ...
  5. // MockitoContextCustomizer 不同的测试类对应的definitions往往并不相同
  6. if (!this.contextCustomizers.equals(otherConfig.contextCustomizers)) {
  7. return false;
  8. }
  9. }

在 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 注解的全定限名。

  1. @ImportAutoConfiguration
  2. public @interface AutoConfigureWebMvc {
  3. }

对应 spring.factories 配置如下:

  1. # AutoConfigureWebMvc auto-configuration imports
  2. org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\
  3. org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
  4. org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
  5. org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
  6. org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
  7. org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
  8. org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
  9. org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
  10. org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
  11. ...

总结时刻

推荐阅读

  1. Test Auto-configuration Annotations》:Spring Boot 官网测试类自动装配。
  2. 26. Spring Boot Testing》:Spring Boot 官网文档。

每天用心记录一点点。内容也许不重要,但习惯很重要!