測試夾具的依賴注入

當你使用 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)。