JUnit 5 @Testcontainers在Cucumber与Spring Boot 3中失效问题排查
在编写Cucumber测试时,发现@Testcontainers注解无法正常工作。使用以下配置时:
import io.cucumber.spring.CucumberContextConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.testcontainers.containers.KafkaContainer; import org.testcontainers.containers.OracleContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; @Testcontainers @CucumberContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //@ActiveProfiles("h2") public class CucumberSpringConfiguration { @Container @ServiceConnection static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-xe:21-slim-faststart"); @Container @ServiceConnection static KafkaContainer kafkaContainer = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.2.1")); }
Spring应用启动失败,报错信息如下:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Mapped port can only be obtained after the container is started at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:177) at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:644) ... 76 more Caused by: java.lang.IllegalStateException: Mapped port can only be obtained after the container is started at org.testcontainers.shaded.com.google.common.base.Preconditions.checkState(Preconditions.java:512) at org.testcontainers.containers.ContainerState.getMappedPort(ContainerState.java:161) at org.testcontainers.containers.OracleContainer.getOraclePort(OracleContainer.java:194) at org.testcontainers.containers.OracleContainer.getJdbcUrl(OracleContainer.java:120) at org.springframework.boot.testcontainers.service.connection.jdbc.JdbcContainerConnectionDetailsFactory$JdbcContainerConnectionDetails.getJdbcUrl(JdbcContainerConnectionDetailsFactory.java:65) at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.createDataSource(DataSourceConfiguration.java:56) at org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari.dataSource(DataSourceConfiguration.java:117) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:140) ... 77 more
核心问题是容器未启动,@Testcontainers注解未生效,JUnit的@BeforeAll/@AfterAll也未被调用。
改用Cucumber的@BeforeAll/@AfterAll手动启动容器后,测试可正常运行:
import io.cucumber.java.AfterAll; import io.cucumber.java.BeforeAll; import io.cucumber.spring.CucumberContextConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.testcontainers.containers.KafkaContainer; import org.testcontainers.containers.OracleContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; @Testcontainers @CucumberContextConfiguration @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //@ActiveProfiles("h2") public class CucumberSpringConfiguration { @Container @ServiceConnection static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-xe:21-slim-faststart"); @Container @ServiceConnection static KafkaContainer kafkaContainer = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:6.2.1")); @BeforeAll public static void setUp() { System.out.println("++++++++ BEFORE ALL ++++++++"); oracleContainer .withReuse(true) .start(); kafkaContainer .withReuse(true) .start(); } @AfterAll public static void tearDown() { System.out.println("++++++++ AFTER ALL ++++++++"); oracleContainer.stop(); kafkaContainer.stop(); } }
环境依赖配置
Maven依赖管理与依赖项
<dependencyManagement> <dependencies> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-bom</artifactId> <version>7.18.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.junit</groupId> <artifactId>junit-bom</artifactId> <version>5.10.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-suite</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-java</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-spring</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-junit-platform-engine</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-testcontainers</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>oracle-xe</artifactId> <version>1.19.8</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>kafka</artifactId> <version>1.19.8</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.19.8</version> <scope>test</scope> </dependency>
Cucumber运行器配置
import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectClasspathResource; import org.junit.platform.suite.api.Suite; import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME; @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example.cucumber") public class CucumberRunnerTest { }
问题原因
注解归属与引擎兼容性问题:
@Testcontainers是JUnit Jupiter的扩展注解,仅由JUnit Jupiter测试引擎处理。当前测试使用的是Cucumber的JUnit Platform引擎,Cucumber不会识别并执行JUnit Jupiter的@Testcontainers、@BeforeAll、@AfterAll注解逻辑,导致容器无法自动启动。Spring上下文初始化时机冲突:Spring Boot启动时,
@ServiceConnection会尝试获取容器的映射端口来配置数据源和Kafka连接,但此时容器未启动(因为@Testcontainers未生效),直接触发"Mapped port can only be obtained after the container is started"异常。Cucumber钩子的有效性:改用Cucumber的
@BeforeAll/@AfterAll后,这些钩子会被Cucumber引擎在测试生命周期中执行,确保容器在Spring上下文初始化前完成启动,端口可用,因此应用能正常初始化并运行测试。
内容的提问来源于stack exchange,提问作者adragomir

