Context Configuration with Dynamic Property Sources

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

The Spring TestContext Framework provides support for dynamic properties via the @DynamicPropertySource annotation and the DynamicPropertyRegistry.

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

The @DynamicPropertySource annotation and its supporting infrastructure were originally designed to allow properties from Testcontainers based tests to be exposed easily to Spring integration tests. However, this feature may be used with any form of external resource whose lifecycle is managed outside the test’s ApplicationContext or with beans whose lifecycle is managed by the test’s ApplicationContext.

与应用于类级别的 @TestPropertySource 注释相反,可以将 @DynamicPropertySource 应用于集成测试类中的 static 方法或测试 @Configuration 类中的 @Bean 方法,以便将具有动态值的属性添加到 EnvironmentPropertySources 的集合,以供集成测试加载的 ApplicationContext

In contrast to the @TestPropertySource annotation that is applied at the class level, @DynamicPropertySource can be applied to static methods in integration test classes or to @Bean methods in test @Configuration classes in order to add properties with dynamic values to the set of PropertySources in the Environment for the ApplicationContext loaded for the integration test.

DynamicPropertyRegistry 用于向 Environment 添加名称-值对。通过仅在解析属性时调用的 Supplier 提供和动态提供值。典型情况下,方法引用用于提供值。

A DynamicPropertyRegistry is used to add name-value pairs to the Environment. Values are dynamic and provided via a Supplier which is only invoked when the property is resolved. Typically, method references are used to supply values.

注释了 @DynamicPropertySource 的集成测试类中的方法必须是 static 的,并且必须接受单个 DynamicPropertyRegistry 参数。

Methods in integration test classes that are annotated with @DynamicPropertySource must be static and must accept a single DynamicPropertyRegistry argument.

@DynamicPropertySource 注释的 @Bean 方法要么接受 type DynamicPropertyRegistry 的参数,要么访问其包含 @Configuration 类中自动注入的 DynamicPropertyRegistry 实例。但请注意,与 DynamicPropertyRegistry 交互的 @Bean 方法不需要用 @DynamicPropertySource 注释,除非它们需要强制上下文中 bean 的热加载。有关详细信息,请参见 DynamicPropertyRegistry 的类级别 javadoc。

@Bean methods annotated with @DynamicPropertySource may either accept an argument of type DynamicPropertyRegistry or access a DynamicPropertyRegistry instance autowired into their enclosing @Configuration class. Note, however, that @Bean methods which interact with a DynamicPropertyRegistry are not required to be annotated with @DynamicPropertySource unless they need to enforce eager initialization of the bean within the context. See the class-level javadoc for DynamicPropertyRegistry for details.

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

If you use @DynamicPropertySource in a base class and discover that tests in subclasses fail because the dynamic properties change between subclasses, you may need to annotate your base class with @DirtiesContext to ensure that each subclass gets its own ApplicationContext with the correct dynamic properties.

以下示例使用 Testcontainers 项目来管理 Spring ApplicationContext 外部的 Redis 容器。受管理的 Redis 容器的 IP 地址和端口通过 redis.hostredis.port 特性向测试的 ApplicationContext 中的组件提供。这些特性可通过 Spring 的 Environment 抽象进行访问,或直接注入 Spring 管理的组件 – 例如,分别通过 @Value("${redis.host}")@Value("${redis.port}")

The following example uses the Testcontainers project to manage a Redis container outside of the Spring ApplicationContext. The IP address and port of the managed Redis container are made available to components within the test’s ApplicationContext via the redis.host and redis.port properties. These properties can be accessed via Spring’s Environment abstraction or injected directly into Spring-managed components – for example, via @Value("${redis.host}") and @Value("${redis.port}"), respectively.

  • 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 中动态检索。

The following example demonstrates how to use DynamicPropertyRegistry and @DynamicPropertySource with a @Bean method. The api.url property can be accessed via Spring’s Environment abstraction or injected directly into other Spring-managed components – for example, via @Value("${api.url}"). The value of the api.url property will be dynamically retrieved from the 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 中获取的动态属性。

The use of @DynamicPropertySource on the @Bean method is optional and results in the ApiServer bean being eagerly initialized so that other beans in the context can be given access to the dynamic properties sourced from the ApiServer bean when those other beans are initialized.

Precedence

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

Dynamic properties have higher precedence than those loaded from @TestPropertySource, the operating system’s environment, Java system properties, or property sources added by the application declaratively by using @PropertySource or programmatically. Thus, dynamic properties can be used to selectively override properties loaded via @TestPropertySource, system property sources, and application property sources.