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

SpringBootTest加载ApplicationContext失败:容器未启动无法获取映射端口

问题描述

项目测试场景分两类使用不同的数据库测试组件:

  • 集成测试依赖:org.testcontainers:postgresql:1.17.2,通过Testcontainers启动真实PostgreSQL容器做测试
  • Repository层单元测试依赖:io.zonky.test:embedded-database-spring-test:2.1.1,用嵌入式PostgreSQL做轻量级Dao层测试

application-it.yml数据库相关配置:

spring:
  main:
    banner-mode: off
  datasource:
    url: jdbc:postgresql://localhost:5432/db
    username: postgres
    password: postgres
  flyway:
    user: flyway
    password: password
    schemas: public
    placeholders:
      user: ${spring.datasource.username}

集成测试基类BaseIT.java代码:

@ActiveProfiles("it")
@SpringBootTest
@Testcontainers
public abstract class BaseIT {

    //...
    // 部分服务注入逻辑
    //...

    static MockWebServer server;

    @Container
    public static PostgreSQLContainer<?> POSTGRESQL_CONTAINER =
            new PostgreSQLContainer<>("postgres:14.3-alpine")
                    .withDatabaseName("db")
                    .withUsername("postgres")
                    .withPassword("postgres")
                    .withInitScript("init.sql");

    @DynamicPropertySource
    static void registerProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRESQL_CONTAINER::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRESQL_CONTAINER::getUsername);
        registry.add("spring.datasource.password", POSTGRESQL_CONTAINER::getPassword);
    }

}

集成测试实现类MyIT.java代码:

@TestPropertySource(properties = {
        "app.scheduling.enable=false"
})
public class MyIT extends BaseIT {

    @BeforeAll
    public static void beforeEachTest() throws IOException {
        server = new MockWebServer();
        server.start(8999);
    }

    @AfterEach
    public void afterEachTest() {
        repository.deleteAll();
    }

    //...
    // 测试用例逻辑
    //...
}

Repository层单元测试类MyUnitTest.java代码:

@Import({
        Service.class
})
@DataJpaTest
@TestPropertySource(properties = {
        "spring.jpa.hibernate.ddl-auto=validate"
})
@ActiveProfiles("it")
@AutoConfigureEmbeddedDatabase(type = AutoConfigureEmbeddedDatabase.DatabaseType.POSTGRES)
public class MyUnitTest {

    @Autowired
    private Repository repository;
    @Autowired
    private Service service;

    //...
    // 测试用例逻辑
    //...
}

运行MyIT时抛出Failed to load ApplicationContext异常,根因是类路径下存在的io.zonky.test.db.EmbeddedDatabaseTestExecutionListener在Testcontainers管理的PostgreSQL容器尚未启动时,就提前读取数据源URL属性,调用容器端口映射方法抛出异常:

java.lang.IllegalStateException: Mapped port can only be obtained after the container is started

移除zonky嵌入式数据库依赖后异常消失,确认是两个测试组件的监听器加载顺序冲突导致。

解决方案

两种方案改动量都很小,且都不会影响单元测试中嵌入式数据库的正常使用,按需选择即可:

方案1(推荐):配置项禁用zonky数据源替换

直接在集成测试基类上添加配置,明确告知zonky组件不需要替换当前测试的数据源,侵入性最低:

@ActiveProfiles("it")
@SpringBootTest
@Testcontainers
// 新增以下配置即可
@TestPropertySource(properties = "zonky.test.database.replace=none")
public abstract class BaseIT {
    // 原有业务逻辑不需要任何修改
}

配置生效后,EmbeddedDatabaseTestExecutionListener不会主动触发数据源替换逻辑,也就不会提前读取未启动容器的端口属性,冲突直接消除。

方案2:排除zonky嵌入式数据库自动配置

如果不希望集成测试上下文加载任何zonky相关组件,可以直接排除对应的自动配置类:

@ActiveProfiles("it")
@SpringBootTest
@Testcontainers
// 新增自动配置排除规则
@EnableAutoConfiguration(exclude = EmbeddedDatabaseAutoConfiguration.class)
public abstract class BaseIT {
    // 原有业务逻辑不需要任何修改
}

使用该方案需要先导入依赖中的类io.zonky.test.db.autoconfigure.EmbeddedDatabaseAutoConfiguration。

两种方案的作用范围仅限集成测试上下文,MyUnitTest类上显式声明的@AutoConfigureEmbeddedDatabase注解会独立触发嵌入式数据库的配置逻辑,单元测试的运行完全不受影响。

内容的提问来源于stack exchange,提问作者user8880523

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:03:21