Context Configuration with Dynamic Property Sources

Spring TestContext 框架通过 @DynamicPropertySource 注释和 DynamicPropertyRegistry 提供对动态特性的支持。

@DynamicPropertySource 注释及其支持基础架构最初旨在允许轻松将基于 Testcontainers 测试的属性公开给 Spring 集成测试。但是,此功能可以与生命周期在测试的 ApplicationContext 之外管理的任何形式的外部资源或由测试的 ApplicationContext 管理生命周期的 bean 一起使用。

与应用于类级别的 @TestPropertySource 注释相反,可以将 @DynamicPropertySource 应用于集成测试类中的 static 方法或测试 @Configuration 类中的 @Bean 方法,以便将具有动态值的属性添加到 EnvironmentPropertySources 的集合,以供集成测试加载的 ApplicationContextDynamicPropertyRegistry 用于向 Environment 添加名称-值对。通过仅在解析属性时调用的 Supplier 提供和动态提供值。典型情况下,方法引用用于提供值。 注释了 @DynamicPropertySource 的集成测试类中的方法必须是 static 的,并且必须接受单个 DynamicPropertyRegistry 参数。 带 @DynamicPropertySource 注释的 @Bean 方法要么接受 type DynamicPropertyRegistry 的参数,要么访问其包含 @Configuration 类中自动注入的 DynamicPropertyRegistry 实例。但请注意,与 DynamicPropertyRegistry 交互的 @Bean 方法不需要用 @DynamicPropertySource 注释,除非它们需要强制上下文中 bean 的热加载。有关详细信息,请参见 DynamicPropertyRegistry 的类级别 javadoc。

如果你在基类中使用 @DynamicPropertySource,并发现子类中的测试失败,因为动态属性在子类之间发生变化,则可能需要使用 @DirtiesContext 注释基类以确保每个子类都能获取到具有正确动态属性的自己的 ApplicationContext

以下示例使用 Testcontainers 项目来管理 Spring ApplicationContext 外部的 Redis 容器。受管理的 Redis 容器的 IP 地址和端口通过 redis.hostredis.port 特性向测试的 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 ...

}

以下示例演示如何将 DynamicPropertyRegistry@DynamicPropertySource@Bean 方法结合使用。可以通过 Spring 的 Environment 抽象访问 api.url 特性,或直接注入其他 Spring 管理的组件 – 例如,通过 @Value("${api.url}")api.url 特性的值将从 ApiServer bean 中动态检索。

  • Java

  • Kotlin

@Configuration
class TestConfig {

	@Bean
	@DynamicPropertySource
	ApiServer apiServer(DynamicPropertyRegistry registry) {
		ApiServer apiServer = new ApiServer();
		registry.add("api.url", apiServer::getUrl);
		return apiServer;
	}
}
@Configuration
class TestConfig {

	@Bean
	@DynamicPropertySource
	fun apiServer(registry: DynamicPropertyRegistry): ApiServer {
		val apiServer = ApiServer()
		registry.add("api.url", apiServer::getUrl)
		return apiServer
	}
}

@Bean 方法上使用 @DynamicPropertySource 的可选,结果是 ApiServer Bean 会被 eagerly 初始化以便上下文中其他 bean 在初始化时能够访问从 ApiServer bean 中获取的动态属性。

Precedence

动态特性比从 @TestPropertySource、操作系统环境、Java 系统特性或通过使用 @PropertySource 声明性地或以编程方式由应用程序添加的特性来源加载的特性具有更高的优先级。因此,动态特性可用于选择性地覆盖通过 @TestPropertySource、系统特性源和应用程序特性源加载的特性。