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 的 0 参数 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 的模拟方式。这包括支持指定模拟如何在测试之间重置,如下例所示

@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,首先必须存在一个被监视类的实际实例。Bean 覆盖功能支持此特殊情况,并允许在实例化 bean 后从元数据创建覆盖,此外还有更常见的替换 bean 定义的情况。

使用您自己的实现进行扩展

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

实现您自己的 Bean 覆盖版本就像实现以下内容一样简单

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

Spring TestContext Framework 解析测试类,查找任何带有 @BeanOverride 元注解的字段,并实例化相关的 BeanOverrideProcessor 以注册 OverrideMetadata 实例。

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

结论

Spring TestContext Framework 现在提供了两种在测试中覆盖 bean 的方法,而没有意外副作用的风险。bean 覆盖机制是可扩展的,例如,如果您喜欢使用 Mockito 以外的模拟库,这可能会派上用场。

我们期待社区对该功能的反馈,包括对首次迭代的改进建议。

同时,祝您编码愉快!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

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

查看所有