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

