在被 @TestConfiguration注解的类中,标注了 @DynamicPropertySource的方法不起作用
我在努力理解Testcontainers以及在Spring Boot应用中有的各种用法。我遇到了一个奇怪的问题,始终弄不清缺少了什么。
下面这个测试类在测试类本身内部使用了 @DynamicPropertySource 注解的方法,结果是可以正常工作。
请注意:我使用的是Spring Boot的最新版本。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoTest {
protected static final PostgreSQLContainer postgresContainer = new PostgreSQLContainer(
DockerImageName.parse("postgres:18.2-alpine3.22")).withExposedPorts(5432)
.withDatabaseName("test_customers_db")
.withUsername("postgres")
.withPassword("postgres");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.driver-class-name", postgresContainer::getDriverClassName);
registry.add("spring.datasource.url", postgresContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgresContainer::getUsername);
registry.add("spring.datasource.password", postgresContainer::getPassword);
}
@BeforeAll
static void beforeAnything() {
postgresContainer.start();
}
@AfterAll
static void afterEverything() {
postgresContainer.stop();
}
@Test
void container_is_running() {
assertTrue(postgresContainer.isRunning());
}
}
我把相同的代码改写如下,结果并不能工作。我只是把 @DynamicPropertySource 注解的方法移到了另一个带有 @TestConfiguration 注解的类中,并在测试类中将它导入。
@TestConfiguration(proxyBeanMethods = false)
class TestDataSourceConfig {
protected static final PostgreSQLContainer postgresContainer = new PostgreSQLContainer(
DockerImageName.parse("postgres:18.2-alpine3.22")).withExposedPorts(5432)
.withDatabaseName("test_customers_db")
.withUsername("postgres")
.withPassword("postgres");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.driver-class-name", postgresContainer::getDriverClassName);
registry.add("spring.datasource.url", postgresContainer::getJdbcUrl);
registry.add("spring.datasource.username", postgresContainer::getUsername);
registry.add("spring.datasource.password", postgresContainer::getPassword);
}
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import(TestDataSourceConfig.class)
class DemoTest {
@BeforeAll
static void beforeAnything() {
postgresContainer.start();
}
@AfterAll
static void afterEverything() {
postgresContainer.stop();
}
@Test
void container_is_running() {
assertTrue(postgresContainer.isRunning());
}
}
所谓“不起作用”是指:
- 位于
configureProperties(...)内的断点没有被触发。 - 测试在以下信息出现时失败。
Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
注:数据源的详细信息来自 test/resources 文件夹中的 application.properties 文件。
spring.datasource.driver-class-name = org.postgresql.Driver
spring.datasource.url = jdbc:postgresql://localhost:5432/test_customers_db
spring.datasource.username = postgres
spring.datasource.password = postgres
我也了解一些其他做法,例如 @Bean、@ServiceConnection,或JUnit Jupiter Testcontainer Extension注解等,但我想弄清楚这里的问题所在。
先行致谢。
解决方案
@DynamicPropertySource 只有在方法直接声明在测试类本身(或其超类/接口)时才起作用。Spring的测试上下文通过扫描测试类层次结构来处理 @DynamicPropertySource——即使从 @TestConfiguration 类导入,也不会被识别。
因此,在你的示例中,如果你想把通用配置外包出去,可以把它放到一个 AbstractIntegrationTestClass 中。
为什么它不起作用
@DynamicPropertySource 机制的设计初衷,是让测试在 ApplicationContext 刷新之前动态定义属性。因为 @Import 在上下文刷新期间被处理,对于框架来说,在那些导入的类中查找动态属性已经为时太晚。