测试夹具的依赖注入

当您使用 DependencyInjectionTestExecutionListener(默认配置)时,测试实例的依赖项将从您使用 @ContextConfiguration 或相关注解配置的应用程序上下文中的 Bean 注入。 您可以使用 setter 注入、字段注入或两者兼用,具体取决于您选择的注解以及将它们放置在 setter 方法还是字段上。 如果您使用的是 JUnit Jupiter,您还可以选择使用构造函数注入 (请参阅 使用 InfraExtension 的依赖注入)。 为了与 Infra 基于注解的注入支持保持一致,您还可以使用 Infra 的 @Autowired 注解或 JSR-330 中的 @Inject 注解进行字段和 setter 注入。

对于 JUnit Jupiter 以外的测试框架,TestContext 框架不参与测试类的实例化。 因此,对构造函数使用 @Autowired@Inject 对测试类没有影响。
虽然在生产代码中不鼓励使用字段注入,但在测试代码中,字段注入实际上是很自然的。 这种差异的理由是,您永远不会直接实例化您的测试类。 因此,不需要能够在测试类上调用 public 构造函数或 setter 方法。

因为 @Autowired 用于执行 按类型自动装配,所以如果您有多个相同类型的 Bean 定义,则不能对这些特定 Bean 依赖此方法。 在这种情况下,您可以将 @Autowired@Qualifier 结合使用。 您也可以选择将 @Inject@Named 结合使用。 或者,如果您的测试类可以访问其 ApplicationContext,则可以通过使用(例如)调用 applicationContext.getBean("titleRepository", TitleRepository.class) 来执行显式查找。

如果您不希望将依赖注入应用于您的测试实例,请不要使用 @Autowired@Inject 注解字段或 setter 方法。 或者,您可以通过使用 @TestExecutionListeners 显式配置您的类并从监听器列表中省略 DependencyInjectionTestExecutionListener.class 来完全禁用依赖注入。

考虑测试 HibernateTitleRepository 类的场景,如 目标 部分所述。 接下来的两个代码清单演示了在字段和 setter 方法上使用 @Autowired。 应用程序上下文配置显示在所有示例代码清单之后。

以下代码清单中的依赖注入行为并非特定于 JUnit Jupiter。 相同的 DI 技术可以与任何支持的测试框架结合使用。

以下示例调用了静态断言方法,例如 assertNotNull(),但在调用前没有加上 Assertions。 在这种情况下,假设该方法已通过示例中未显示的 import static 声明正确导入。

第一个代码清单显示了基于 JUnit Jupiter 的测试类实现,该实现使用 @Autowired 进行字段注入:

  • Java

@ExtendWith(InfraExtension.class)
// 指定为此测试夹具加载的 Infra 配置
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

  // 这个实例将通过类型进行依赖注入
  @Autowired
  HibernateTitleRepository titleRepository;

  @Test
  void findById() {
    Title title = titleRepository.findById(new Long(10));
    assertNotNull(title);
  }
}

或者,您可以配置该类以对 setter 注入使用 @Autowired,如下所示:

  • Java

@ExtendWith(InfraExtension.class)
// 指定为此测试夹具加载的 Infra 配置
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

  // 这个实例将通过类型进行依赖注入
  HibernateTitleRepository titleRepository;

  @Autowired
  void setTitleRepository(HibernateTitleRepository titleRepository) {
    this.titleRepository = titleRepository;
  }

  @Test
  void findById() {
    Title title = titleRepository.findById(new Long(10));
    assertNotNull(title);
  }
}

前面的代码清单使用了 @ContextConfiguration 注解引用的相同 XML 上下文文件(即 repository-config.xml)。 以下显示了此配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

  <!-- 这个 bean 将被注入到 HibernateTitleRepositoryTests 类中 -->
  <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>

  <bean id="sessionFactory" class="infra.orm.hibernate5.LocalSessionFactoryBean">
    <!-- 省略配置以简洁 -->
  </bean>

</beans>

如果您是从 Infra 提供的测试基类扩展而来,而该基类碰巧在其 setter 方法之一上使用了 @Autowired,则您的应用程序上下文中可能会定义多个受影响类型的 Bean(例如,多个 DataSource Bean)。 在这种情况下,您可以重写 setter 方法并使用 @Qualifier 注解来指示特定的目标 Bean,如下所示(但请确保也委托给超类中的重写方法):

  • Java

// ...

  @Autowired
  @Override
  public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
    super.setDataSource(dataSource);
  }

// ...

指定的限定符值指示要注入的特定 DataSource Bean,将类型匹配集缩小到特定 Bean。 其值与相应的 <bean> 定义中的 <qualifier> 声明进行匹配。 Bean 名称用作回退限定符值,因此您实际上也可以通过名称指向特定 Bean(如前所示,假设 myDataSource 是 Bean id)。