运行Gradle集成测试任务时Test Container连接失败问题
集成测试批量执行时JDBC连接拒绝问题
单独运行单个测试类或测试用例时一切正常,但执行Gradle任务运行所有集成测试时,抛出如下JDBC连接异常:
org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection
Connection to localhost:50234 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
相关代码实现
集成测试基类(Java)
@ActiveProfiles("itest") @Testcontainers @Import({DatabaseCleaner.class, IntegrationTestSetup.class}) @ContextConfiguration(initializers = IntegrationTestBase.ContextInitializer.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class IntegrationTestBase { @Autowired public DatabaseCleaner databaseCleaner; @BeforeEach public void globalBeforeEach() { databaseCleaner.truncate(); } @Container static final PostgreSQLContainer postgresTestContainer = new PostgreSQLContainer("postgres:15") .withUsername("itest") .withPassword("itest") .withDatabaseName("catsit"); public static class ContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { final String url = postgresTestContainer.getJdbcUrl(); final String username = postgresTestContainer.getUsername(); final String password = postgresTestContainer.getPassword(); TestPropertySourceUtils.addInlinedPropertiesToEnvironment( configurableApplicationContext, "spring.datasource.url=" + url, "spring.datasource.username=" + username, "spring.datasource.password=" + password, "spring.liquibase.url=" + url, "spring.liquibase.user=" + username, "spring.liquibase.password=" + password, "spring.liquibase.change-log=" + "classpath:db/changelog/master.xml" ); } } }
Gradle测试配置(Groovy)
testing { suites { test { useJUnitJupiter() } itest(JvmTestSuite) { testType = TestSuiteType.INTEGRATION_TEST dependencies { implementation project() } configurations { itestImplementation.extendsFrom testImplementation itestRuntime.extendsFrom testRuntime itestRuntimeOnly.extendsFrom testRuntimeOnly } } } }
问题原因与解决方法
核心原因
批量执行测试时,Testcontainers容器生命周期管理出现冲突:
- 基类中静态
@Container容器被所有子类共享,但Gradle并行执行测试时,不同测试类可能尝试复用已关闭的容器连接 - 自定义上下文初始化器直接获取容器URL时,容器可能未完全启动,或多线程环境下端口被复用、容器已销毁
修复方案
方案1:改用@DynamicPropertySource动态注入属性
移除自定义的ContextInitializer,使用Spring Boot提供的@DynamicPropertySource动态绑定容器属性,确保容器状态与属性注入同步:
@ActiveProfiles("itest") @Testcontainers @Import({DatabaseCleaner.class, IntegrationTestSetup.class}) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class IntegrationTestBase { @Autowired public DatabaseCleaner databaseCleaner; @BeforeEach public void globalBeforeEach() { databaseCleaner.truncate(); } @Container static final PostgreSQLContainer postgresTestContainer = new PostgreSQLContainer("postgres:15") .withUsername("itest") .withPassword("itest") .withDatabaseName("catsit"); @DynamicPropertySource static void registerPgProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgresTestContainer::getJdbcUrl); registry.add("spring.datasource.username", postgresTestContainer::getUsername); registry.add("spring.datasource.password", postgresTestContainer::getPassword); registry.add("spring.liquibase.url", postgresTestContainer::getJdbcUrl); registry.add("spring.liquibase.user", postgresTestContainer::getUsername); registry.add("spring.liquibase.password", postgresTestContainer::getPassword); } }
方案2:确保容器懒加载且全局唯一
将容器改为懒加载单例模式,避免提前初始化导致的端口冲突:
// 替换原静态容器定义 private static PostgreSQLContainer postgresTestContainer; @Container public static PostgreSQLContainer getPostgresTestContainer() { if (postgresTestContainer == null || !postgresTestContainer.isRunning()) { postgresTestContainer = new PostgreSQLContainer("postgres:15") .withUsername("itest") .withPassword("itest") .withDatabaseName("catsit"); } return postgresTestContainer; }
方案3:临时禁用Gradle并行测试
若暂时不想修改代码,可在gradle.properties中添加以下配置关闭并行执行:
org.gradle.parallel=false
此方法会降低测试效率,仅作为临时应急方案。
额外优化建议
- 给容器添加
withReuse(true)(需开启Testcontainers复用模式),减少重复启动容器的开销 - 确保所有集成测试类都正确继承
IntegrationTestBase,避免重复创建容器实例
内容的提问来源于stack exchange,提问作者dev-rifaii
相关产品推荐
相关产品推荐

