Spring Framework 6.2.0-M1:在测试中覆盖 Bean

工程 | Simon Baslé | 2024 年 4 月 16 日 | ...

Spring Framework 6.2.0-M1 已发布,其中包含解决一百多个问题的更改。这些更改中包括 Spring 测试支持中的一系列新特性。

在这篇文章中,我想向大家介绍这些新测试特性中的一个:Bean 覆盖支持。

现状回顾

使用 Spring TestContext Framework,您可以通过注解驱动的模型轻松地在集成测试中验证 Spring 应用程序的正确连线。

在单元测试中,依赖注入和 Spring 设计原则使您的代码对容器的依赖性降低,并且更容易手动桩或模拟组件的依赖项以便进行隔离测试。在集成测试中,这一点不太相关,因为测试旨在覆盖组件的正确连线。尽管您可能会发现在集成测试中需要替换 bean 的情况。

Spring Framework 团队通常不建议重新定义 bean。尽管在 `BeanDefinitionRegistry` 的默认实现中通过一个标志目前可以实现,但我们计划弃用它,并且 Spring Boot 已经通过默认关闭 bean 覆盖来选择退出。

然而,这更多是生产代码中的担忧,我们承认在测试中覆盖 bean 是有用且合理的。因此,我们旨在为该领域常见的覆盖场景提供一流的安排。

在 Spring Framework `6.2.0-M1` 中,我们引入了一个可扩展的 bean 覆盖特性,它允许您在集成测试中精确且明确地替换一个或多个 bean 定义,同时防止生产代码或测试的其他部分出现此类意外更改。

使用 @TestBean 进行简单的基于方法的覆盖

Spring TestContext Framework 现在提供了一个简单的 Bean 覆盖支持实现:`@TestBean` 注解。

覆盖名为 `example` 的 bean 分三步完成:添加一个与 bean 同名的字段,用 `@TestBean` 注解它,并添加一个名为 `exampleTestOverride` 的无参数 `static` 工厂方法。在该工厂方法中,如果 bean 类型是接口,您可以返回一个简化的实现,如下例所示:

@Configuration
class ProdConfiguration {

  @Bean
  MyService customService() {
    return new ProdServiceImpl();
  }
}

@SpringJUnitConfig
class MyServiceIntegrationTests {

  @TestBean
  MyService customService;

  static MyService customServiceTestOverride() {
    return new SimplifiedServiceImpl();
  }

  @Test
  void test(ApplicationContext context) {
    assertThat(context.getBean("customService")
      .isSameAs(this.customService)
      .isInstanceOf(SimplifiedServiceImpl.class);
    //...
  }
}

除非为 `@TestBean` 注解提供了 `beanName` 属性,否则带注解字段的名称将被解释为目标 bean 的名称。

`methodName` 参数也可用于指向不遵循默认命名约定 `{beanName}TestOverride` 的工厂方法。

Bean 覆盖机制负责解析此注解并替换注册表中的现有 bean 定义。测试类中的 `customService` 字段也会注入由 `customServiceTestOverride` 工厂方法生成的覆盖实例。

使用 @MockitoBean@MockitoSpyBean 进行基于 Mockito 的覆盖

这第二个 bean 覆盖实现基于 Mockito 库。它带有两个注解:@MockitoBean 用于自动将目标单例 bean 替换为 mock,以及 @MockitoSpyBean 用于将 bean 包装在 spy 中。

这些注解都包含 Mockito 特有的属性,以便进一步配置目标 bean 如何进行 mocking。这包括支持指定 mock 在测试之间如何重置,如下例所示:

@Configuration
class ProdConfiguration {

  @Bean
  MyService customService() {
    return new ProdService();
  }
}

@SpringJUnitConfig
class MyServiceIntegrationTests {

  @MockitoSpyBean(reset = MockReset.NONE)
  MyService customService;

  @Test
  void test() {
    //...
  }
}

在上面的示例中,spy 在测试之间将不会重置。默认情况下,mock 和 spy 会在测试方法运行重置。

请注意,要对 bean 进行 spy,首先必须存在被 spy 类的一个实际实例。Bean 覆盖特性支持这种特殊情况,允许在 bean 实例化后从元数据创建覆盖,此外还支持更常见的替换 bean 定义的情况。

扩展您自己的实现

新的测试中的 Bean 覆盖支持采用基于注解的模型,适用于您测试类中的字段。它是可扩展和可定制的,上面介绍的 3 个注解只是我们提供的默认实现。

实现您自己的 Bean 覆盖方式就像实现以下内容一样简单:

  • 一个用 `@BeanOverride` 元注解标记的注解,该注解定义了要使用的 `BeanOverrideProcessor`。
  • `BeanOverrideProcessor` 实现本身。
  • 由处理器提供的一个或多个具体的 `OverrideMetadata` 实现。

Spring TestContext Framework 解析测试类,查找任何用 `@BeanOverride` 元注解标记的字段,并实例化相关的 `BeanOverrideProcessor` 以注册一个 `OverrideMetadata` 实例。

然后 `BeanFactoryPostProcessor` 将使用该信息修改上下文,根据每个元数据注册和替换 bean 定义。

结论

Spring TestContext Framework 现在提供了两种在测试中覆盖 bean 的方法,而没有意外副作用的风险。bean 覆盖机制是可扩展的,如果您更喜欢使用 Mockito 以外的 mocking 库,这将非常方便。

我们期待社区对这项功能的反馈,包括对第一版改进的建议。

与此同时,编程愉快!

订阅 Spring 电子报

订阅 Spring 电子报,保持连接

订阅

领先一步

VMware 提供培训和认证,为您的发展注入动力。

了解更多

获得支持

Tanzu Spring 通过一个简单的订阅,为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部