测试夹具的依赖注入

当你使用 DependencyInjectionTestExecutionListener(默认已配置)时,你的测试实例的依赖项会从你使用 @ContextConfiguration 或相关注解配置的应用上下文中的 bean 进行注入。你可以使用 Setter 注入、字段注入或两者结合,具体取决于你选择的注解以及你将它们放置在 Setter 方法还是字段上。如果你使用 JUnit Jupiter,你还可以选择使用构造函数注入(参见使用 SpringExtension 进行依赖注入)。为了与 Spring 基于注解的注入支持保持一致,你也可以使用 Spring 的 @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)

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

考虑测试 HibernateTitleRepository 类的情况,如目标部分所述。下面两个代码清单演示了在字段和 Setter 方法上使用 @Autowired。应用上下文配置在所有示例代码清单之后呈现。

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

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

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

  • Java

  • Kotlin

@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

	// this instance will be dependency injected by type
	@Autowired
	HibernateTitleRepository titleRepository;

	@Test
	void findById() {
		Title title = titleRepository.findById(new Long(10));
		assertNotNull(title);
	}
}
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

	// this instance will be dependency injected by type
	@Autowired
	lateinit var titleRepository: HibernateTitleRepository

	@Test
	fun findById() {
		val title = titleRepository.findById(10)
		assertNotNull(title)
	}
}

或者,你可以配置类使用 @Autowired 进行 Setter 注入,如下所示

  • Java

  • Kotlin

@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

	// this instance will be dependency injected by type
	HibernateTitleRepository titleRepository;

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

	@Test
	void findById() {
		Title title = titleRepository.findById(new Long(10));
		assertNotNull(title);
	}
}
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

	// this instance will be dependency injected by type
	lateinit var titleRepository: HibernateTitleRepository

	@Autowired
	fun setTitleRepository(titleRepository: HibernateTitleRepository) {
		this.titleRepository = titleRepository
	}

	@Test
	fun findById() {
		val title = titleRepository.findById(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">

	<!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
	<bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>

	<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
		<!-- configuration elided for brevity -->
	</bean>

</beans>

如果你从一个恰好在其 Setter 方法上使用 @Autowired 的 Spring 提供的测试基类继承,你的应用上下文中可能定义了多个受影响类型的 Bean(例如,多个 DataSource Bean)。在这种情况下,你可以覆盖 Setter 方法,并使用 @Qualifier 注解来指示特定的目标 Bean,如下所示(但也要确保委托给父类中被覆盖的方法)

  • Java

  • Kotlin

// ...

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

// ...
// ...

	@Autowired
	override fun setDataSource(@Qualifier("myDataSource") dataSource: DataSource) {
		super.setDataSource(dataSource)
	}

// ...

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