带有动态属性源的上下文配置
Spring TestContext框架通过DynamicPropertyRegistry、@DynamicPropertySource注解以及DynamicPropertyRegistrar API提供了对动态属性的支持。
动态属性源基础设施最初的设计目的是为了让基于 Testcontainers 的测试中的属性能够方便地被Spring集成测试所使用。然而,这些功能也可以用于任何生命周期由测试的ApplicationContext之外管理的外部资源,或者用于那些生命周期由测试的ApplicationContext管理的Bean。
优先级
动态属性的优先级高于通过 @TestPropertySource 加载的属性、操作系统的环境变量、Java 系统属性,以及应用程序通过 @PropertySource 声明式添加或通过编程方式添加的属性源。因此,动态属性可以用来选择性地覆盖通过 @TestPropertySource 加载的属性、系统属性源和应用程序属性源中的属性。
DynamicPropertyRegistry
DynamicPropertyRegistry 用于向 Environment 中添加 名称-值 对。这些值是动态的,通过一个 Supplier 提供,而该 Supplier 仅在属性被解析时才会被调用。通常,使用方法引用来提供这些值。以下部分提供了如何使用 DynamicPropertyRegistry 的示例。
@DynamicPropertySource
与在类级别应用的@TestPropertySource注解不同,@DynamicPropertySource可以应用于集成测试类中的static方法,以便向为集成测试加载的ApplicationContext的Environment中的PropertySources集合中添加具有动态值的属性。
在用@DynamicPropertySource注释的集成测试类中,方法必须是static的,并且必须接受一个DynamicPropertyRegistry类型的参数。有关更多详细信息,请参阅DynamicPropertyRegistry的类级javadoc。
如果在基类中使用了@DynamicPropertySource,但发现子类中的测试失败了(因为动态属性在子类之间发生了变化),那么你可能需要在基类上添加@DirtiesContext注解,以确保每个子类都能获得带有正确动态属性的自己的ApplicationContext。
以下示例使用Testcontainers项目来管理Spring ApplicationContext之外的Redis容器。通过redis.host和redis.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 ...
}
DynamicProperty Registrar
作为在集成测试类中实现@DynamicPropertySource方法的一种替代方案,您可以在测试的ApplicationContext中注册DynamicPropertyRegistrar API的实现作为bean。这样做可以支持@DynamicPropertySource方法无法实现的额外用例。例如,由于DynamicProperty Registrar本身是ApplicationContext中的一个bean,它可以与上下文中的其他bean交互,并注册从这些bean中获取的动态属性。
在测试的ApplicationContext中,任何实现DynamicPropertyRegistrar接口的Bean都会被自动检测到,并在单例预实例化阶段之前被积极初始化(即提前进行初始化)。这些Bean的accept()方法将会被调用,此时会使用一个DynamicPropertyRegistry来代表注册器执行实际的动态属性注册操作。
与其他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)
}
}