使用動態屬性源進行上下文配置

Spring TestContext Framework 透過 DynamicPropertyRegistry@DynamicPropertySource 註解和 DynamicPropertyRegistrar API 支援動態屬性。

動態屬性源基礎設施最初設計用於將基於 Testcontainers 的測試中的屬性輕鬆暴露給 Spring 整合測試。然而,這些功能也可用於任何生命週期在測試的 ApplicationContext 外部管理的外部資源,或者生命週期由測試的 ApplicationContext 管理的 bean。

優先順序

動態屬性的優先順序高於從 @TestPropertySource、作業系統環境變數、Java 系統屬性,或應用透過宣告式(使用 @PropertySource)或程式化方式新增的屬性源載入的屬性。因此,動態屬性可用於選擇性地覆蓋透過 @TestPropertySource、系統屬性源和應用屬性源載入的屬性。

DynamicPropertyRegistry

DynamicPropertyRegistry 用於向 Environment 新增鍵值對。這些值是動態的,透過一個 Supplier 提供,該 Supplier 僅在屬性被解析時呼叫。通常使用方法引用來提供值。以下各節提供瞭如何使用 DynamicPropertyRegistry 的示例。

@DynamicPropertySource

與應用於類級別的 @TestPropertySource 註解不同,@DynamicPropertySource 可以應用於整合測試類中的 static 方法,以便將具有動態值的屬性新增到為該整合測試載入的 ApplicationContextEnvironment 中的 PropertySources 集合中。

整合測試類中用 @DynamicPropertySource 註解的方法必須是 static 的,並且必須接受一個 DynamicPropertyRegistry 引數。有關更多詳細資訊,請參閱 DynamicPropertyRegistry 的類級別 javadoc。

如果在基類中使用 @DynamicPropertySource,並且發現子類中的測試失敗,因為動態屬性在子類之間發生變化,則您可能需要在基類上使用 @DirtiesContext 註解,以確保每個子類都擁有自己的具有正確動態屬性的 ApplicationContext

以下示例使用 Testcontainers 專案來管理 Spring ApplicationContext 外部的 Redis 容器。透過 redis.hostredis.port 屬性,託管 Redis 容器的 IP 地址和埠可供測試的 ApplicationContext 中的元件訪問。這些屬性可以透過 Spring 的 Environment 抽象訪問,或者直接注入到 Spring 管理的元件中——例如,分別透過 @Value("${redis.host}")@Value("${redis.port}")

  • Java

  • Kotlin

@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

	@Container
	static GenericContainer redis =
		new GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379);

	@DynamicPropertySource
	static void redisProperties(DynamicPropertyRegistry registry) {
		registry.add("redis.host", redis::getHost);
		registry.add("redis.port", redis::getFirstMappedPort);
	}

	// tests ...

}
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

	companion object {

		@Container
		@JvmStatic
		val redis: GenericContainer =
			GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379)

		@DynamicPropertySource
		@JvmStatic
		fun redisProperties(registry: DynamicPropertyRegistry) {
			registry.add("redis.host", redis::getHost)
			registry.add("redis.port", redis::getFirstMappedPort)
		}
	}

	// tests ...

}

DynamicPropertyRegistrar

除了在整合測試類中實現 @DynamicPropertySource 方法之外,您還可以將 DynamicPropertyRegistrar API 的實現註冊為測試的 ApplicationContext 中的 bean。這樣做可以支援使用 @DynamicPropertySource 方法無法實現的額外用例。例如,由於 DynamicPropertyRegistrar 本身是 ApplicationContext 中的一個 bean,它可以與上下文中的其他 bean 互動,並註冊來源於這些 bean 的動態屬性。

測試的 ApplicationContext 中實現 DynamicPropertyRegistrar 介面的任何 bean 都將在單例預例項化階段之前自動檢測並急切初始化,並且將使用代表註冊器執行實際動態屬性註冊的 DynamicPropertyRegistry 呼叫這些 bean 的 accept() 方法。

與任何其他 bean 的互動都會導致這些其他 bean 及其依賴項的急切初始化。

以下示例演示瞭如何將 DynamicPropertyRegistrar 實現為一個 lambda 表示式,該表示式為 ApiServer bean 註冊一個動態屬性。可以透過 Spring 的 Environment 抽象訪問 api.url 屬性,或直接注入到其他 Spring 管理的元件中——例如,透過 @Value("${api.url}"),並且 api.url 屬性的值將從 ApiServer bean 動態獲取。

  • Java

  • Kotlin

@Configuration
class TestConfig {

	@Bean
	ApiServer apiServer() {
		return new ApiServer();
	}

	@Bean
	DynamicPropertyRegistrar apiPropertiesRegistrar(ApiServer apiServer) {
		return registry -> registry.add("api.url", apiServer::getUrl);
	}
}
@Configuration
class TestConfig {

	@Bean
	fun apiServer(): ApiServer {
		return ApiServer()
	}

	@Bean
	fun apiPropertiesRegistrar(apiServer: ApiServer): DynamicPropertyRegistrar {
		return registry -> registry.add("api.url", apiServer::getUrl)
	}
}