使用动态属性源进行上下文配置

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方法,以便向EnvironmentApplicationContextPropertySources集合添加具有动态值的属性(为集成测试加载的ApplicationContext)。

@DynamicPropertySource注解的集成测试类中的方法必须是static的,并且必须接受单个DynamicPropertyRegistry参数。有关更多详细信息,请参阅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 ...

}

DynamicPropertyRegistrar

作为在集成测试类中实现@DynamicPropertySource方法的替代方法,您可以将DynamicPropertyRegistrar API 的实现注册为测试的ApplicationContext中的 Bean。这样做可以支持@DynamicPropertySource方法无法实现的其他用例。例如,由于DynamicPropertyRegistrar本身是ApplicationContext中的一个 Bean,因此它可以与上下文中的其他 Bean 交互并注册从这些 Bean 获取的动态属性。

测试的ApplicationContext中实现DynamicPropertyRegistrar接口的任何 Bean 都将自动检测并在单例预实例化阶段之前急切地初始化,并且此类 Bean 的accept()方法将使用执行实际动态属性注册的DynamicPropertyRegistry来调用。

与其他 Bean 的任何交互都会导致急切初始化这些其他 Bean 及其依赖项。

以下示例演示如何将DynamicPropertyRegistrar实现为注册ApiServer Bean 的动态属性的 lambda 表达式。可以通过 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)
	}
}