使用測試屬性源的上下文配置

Spring Framework 對具有屬性源層級結構的環境概念提供一流的支援,您可以使用測試專用的屬性源配置整合測試。與 @Configuration 類上使用的 @PropertySource 註解不同,您可以在測試類上宣告 @TestPropertySource 註解,以宣告測試屬性檔案或內聯屬性的資源位置。這些測試屬性源將被新增到為帶註解的整合測試載入的 ApplicationContextEnvironment 中的 PropertySources 集合中。

您可以將 @TestPropertySourceSmartContextLoader SPI 的任何實現一起使用,但 @TestPropertySource 不支援使用舊的 ContextLoader SPI 實現。

SmartContextLoader 的實現可以透過 MergedContextConfiguration 中的 getPropertySourceDescriptors()getPropertySourceProperties() 方法訪問合併的測試屬性源值。

宣告測試屬性源

您可以使用 @TestPropertySourcelocationsvalue 屬性配置測試屬性檔案。

預設情況下,支援傳統的和基於 XML 的 java.util.Properties 檔案格式,例如 "classpath:/com/example/test.properties""file:///path/to/file.xml"。從 Spring Framework 6.1 開始,您可以透過 @TestPropertySource 中的 factory 屬性配置自定義的 PropertySourceFactory,以支援不同的檔案格式,例如 JSON、YAML 等。

每個路徑都被解釋為一個 Spring Resource。一個普通路徑(例如 "test.properties")被視為相對於定義測試類的包的類路徑資源。以斜槓開頭的路徑被視為絕對類路徑資源(例如:"/org/example/test.xml")。引用 URL 的路徑(例如,以 classpath:file:http: 開頭的路徑)使用指定的資源協議載入。

路徑中的屬性佔位符(例如 ${…​})將根據 Environment 解析。

從 Spring Framework 6.1 開始,還支援資源位置模式,例如 "classpath*:/config/*.properties"

以下示例使用了一個測試屬性檔案

  • Java

  • Kotlin

@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
	// class body...
}
1 指定具有絕對路徑的屬性檔案。
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
	// class body...
}
1 指定具有絕對路徑的屬性檔案。

您可以使用 @TestPropertySourceproperties 屬性以鍵值對的形式配置內聯屬性,如下一個示例所示。所有鍵值對都將作為單個具有最高優先順序的測試 PropertySource 新增到封閉的 Environment 中。

支援的鍵值對語法與 Java 屬性檔案中定義的條目語法相同

  • key=value

  • key:value

  • key value

儘管可以使用上述任何語法變體以及鍵和值之間的任意數量的空格來定義屬性,但建議您在測試套件中只使用一種語法變體並保持一致的間距——例如,始終考慮使用 key = value 而不是 key= valuekey=value 等。類似地,如果您使用文字塊定義內聯屬性,則應在整個測試套件中始終使用文字塊來定義內聯屬性。

原因是您提供的確切字串將用於確定上下文快取的鍵。因此,為了受益於上下文快取,您必須確保始終一致地定義內聯屬性。

以下示例設定了兩個內聯屬性

  • Java

  • Kotlin

@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port = 4242"}) (1)
class MyIntegrationTests {
	// class body...
}
1 透過字串陣列設定兩個屬性。
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port = 4242"]) (1)
class MyIntegrationTests {
	// class body...
}
1 透過字串陣列設定兩個屬性。

從 Spring Framework 6.1 開始,您可以使用 文字塊 在單個 String 中定義多個內聯屬性。以下示例使用文字塊設定了兩個內聯屬性

  • Java

  • Kotlin

@ContextConfiguration
@TestPropertySource(properties = """
	timezone = GMT
	port = 4242
	""") (1)
class MyIntegrationTests {
	// class body...
}
1 透過文字塊設定兩個屬性。
@ContextConfiguration
@TestPropertySource(properties = ["""
	timezone = GMT
	port = 4242
	"""]) (1)
class MyIntegrationTests {
	// class body...
}
1 透過文字塊設定兩個屬性。

@TestPropertySource 可以用作 可重複註解

這意味著您可以在單個測試類上擁有多個 @TestPropertySource 宣告,其中後一個 @TestPropertySource 註解中的 locationsproperties 會覆蓋前一個 @TestPropertySource 註解中的同名配置。

此外,您可以在測試類上宣告多個組合註解,每個組合註解都元註解了 @TestPropertySource,並且所有這些 @TestPropertySource 宣告都會貢獻到您的測試屬性源中。

直接存在的 @TestPropertySource 註解始終優先於元註解中存在的 @TestPropertySource 註解。換句話說,直接存在的 @TestPropertySource 註解中的 locationsproperties 將覆蓋用作元註解的 @TestPropertySource 註解中的 locationsproperties

預設屬性檔案檢測

如果將 @TestPropertySource 宣告為空註解(即,沒有為 locationsproperties 屬性指定顯式值),則會嘗試檢測相對於宣告該註解的類的預設屬性檔案。例如,如果帶註解的測試類是 com.example.MyTest,則相應的預設屬性檔案是 classpath:com/example/MyTest.properties。如果無法檢測到預設檔案,則會丟擲 IllegalStateException

優先順序

測試屬性比作業系統環境、Java 系統屬性或應用程式透過 @PropertySource 宣告式新增或透過程式設計方式新增的屬性源具有更高的優先順序。因此,測試屬性可用於選擇性地覆蓋從系統和應用程式屬性源載入的屬性。此外,內聯屬性比從資源位置載入的屬性具有更高的優先順序。但請注意,透過 @DynamicPropertySource 註冊的屬性比透過 @TestPropertySource 載入的屬性具有更高的優先順序。

在下一個示例中,timezoneport 屬性以及 "/test.properties" 中定義的任何屬性會覆蓋系統和應用程式屬性源中定義的任何同名屬性。此外,如果 "/test.properties" 檔案為 timezoneport 屬性定義了條目,這些條目將被使用 properties 屬性宣告的內聯屬性覆蓋。以下示例展示瞭如何在檔案中和內聯方式同時指定屬性

  • Java

  • Kotlin

@ContextConfiguration
@TestPropertySource(
	locations = "/test.properties",
	properties = {"timezone = GMT", "port = 4242"}
)
class MyIntegrationTests {
	// class body...
}
@ContextConfiguration
@TestPropertySource("/test.properties",
		properties = ["timezone = GMT", "port = 4242"]
)
class MyIntegrationTests {
	// class body...
}

繼承和覆蓋測試屬性源

@TestPropertySource 支援布林屬性 inheritLocationsinheritProperties,它們表示是否應繼承超類宣告的屬性檔案資源位置和內聯屬性。這兩個標誌的預設值都是 true。這意味著測試類會繼承任何超類宣告的位置和內聯屬性。具體來說,測試類的位置和內聯屬性將附加到超類宣告的位置和內聯屬性之後。因此,子類可以選擇擴充套件位置和內聯屬性。請注意,後出現的屬性會覆蓋(即 shadow)先出現的同名屬性。此外,上述優先順序規則也適用於繼承的測試屬性源。

如果 @TestPropertySource 中的 inheritLocationsinheritProperties 屬性設定為 false,則測試類的位置或內聯屬性將分別覆蓋並實際替換超類定義的配置。

測試配置也可以從封閉類繼承。有關詳細資訊,請參見 @Nested 測試類配置

在下一個示例中,BaseTestApplicationContext 僅使用 base.properties 檔案作為測試屬性源載入。相比之下,ExtendedTestApplicationContext 使用 base.propertiesextended.properties 檔案作為測試屬性源位置載入。以下示例展示瞭如何透過 properties 檔案在子類及其超類中定義屬性

  • Java

  • Kotlin

@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
	// ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
	// ...
}
@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
	// ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest : BaseTest() {
	// ...
}

在下一個示例中,BaseTestApplicationContext 僅使用內聯的 key1 屬性載入。相比之下,ExtendedTestApplicationContext 使用內聯的 key1key2 屬性載入。以下示例展示瞭如何透過內聯屬性在子類及其超類中定義屬性

  • Java

  • Kotlin

@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
	// ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
	// ...
}
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
	// ...
}

@TestPropertySource(properties = ["key2 = value2"])
@ContextConfiguration
class ExtendedTest : BaseTest() {
	// ...
}