测试固件的依赖注入

当您使用DependencyInjectionTestExecutionListener(默认配置)时,您的测试实例的依赖项将从应用程序上下文中的bean中注入,您可以使用@ContextConfiguration或相关注解配置该应用程序上下文。您可以根据选择的注解以及将它们放置在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)来执行显式查找。

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

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

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

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

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

  • Java

  • Kotlin

@ExtendWith(SpringExtension.class)
// 指定要加载的Spring配置以用于此测试固件
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

	// 此实例将按类型进行依赖注入
	@Autowired
	HibernateTitleRepository titleRepository;

	@Test
	void findById() {
		Title title = titleRepository.findById(new Long(10));
		assertNotNull(title);
	}
}
@ExtendWith(SpringExtension::class)
// 指定要加载的Spring配置以用于此测试固件
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

	// 此实例将按类型进行依赖注入
	@Autowired
	lateinit var titleRepository: HibernateTitleRepository

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

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

  • Java

  • Kotlin

@ExtendWith(SpringExtension.class)
// 指定要加载的Spring配置以用于此测试固件
@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);
	}
}
@ExtendWith(SpringExtension::class)
// 指定要加载的Spring配置以用于此测试固件
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

	// 此实例将按类型进行依赖注入
	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">

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

	<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
		<!-- 为简洁起见省略了配置 -->
	</bean>

</beans>

如果您正在扩展Spring提供的测试基类,该基类恰好在其中一个setter方法上使用了@Autowired,则您的应用程序上下文中可能定义了多个受影响类型的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)。