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 参数静态工厂方法。例如,如果 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,@MockitoSpyBean用于将 Bean 包装在间谍中。

这些注解中的每一个都有特定于 Mockito 的属性,以便进一步配置目标 Bean 的模拟方式。这包括支持指定测试之间如何重置模拟,如下例所示

@Configuration
class ProdConfiguration {

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

@SpringJUnitConfig
class MyServiceIntegrationTests {

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

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

在上面的示例中,间谍在测试之间_不会_重置。默认情况下,模拟和间谍在测试方法运行_之后_重置。

请注意,为了监视 Bean,首先必须存在被监视类的实际实例。Bean 覆盖功能支持这种情况,并且允许在 Bean 实例化后从元数据创建覆盖,这除了更常见的替换 Bean 定义的情况之外。

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

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

实现您自己的 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 社区中所有即将举行的活动。

查看全部