You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

运行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:45:36