Я написал тест на огурец и заметил, что @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"));
}
Весеннее приложение завершится неудачей:
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, похоже, ничего не делает. Я также заметил, что если я добавлю этот класс @BeforeAll/ @AfterAll из пакета junit, они вообще не будут вызываться, поэтому интеграция junit кажется мне довольно мертвой.
Теперь, если я вручную запущу контейнеры в @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();
}
}
Обратите внимание, что я использую BeforeAll/AfterAll из пакета огурца, а не junit.
Во всех уроках, которые я видел в Интернете, @Testcontainer отлично работает с огурцом.
Я использую Spring Boot 3.3.0 и следующие настройки Junit/огурца:
<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>
Класс конфигурации огурца выглядит так:
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, JUnit 5 состоит из нескольких разных модулей из трех разных подпроектов.
JUnit 5 = Платформа JUnit + JUnit Jupiter + JUnit Vintage
JUnit Jupiter и JUnit Vintage — тестовые движки на платформе JUnit. Точно так же, как JUnit Suite (который запускает аннотацию @Suite), а также Cucumber. Вообще говоря, это означает, что функции, являющиеся частью Jupiter, не будут работать ни на одном из других тестовых двигателей.
Например, если посмотреть исходный код org.testcontainers.junit.jupiter.Testcontainers:
package org.testcontainers.junit.jupiter;
import org.junit.jupiter.api.extension.ExtendWith;
...
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(TestcontainersExtension.class)
@Inherited
public @interface Testcontainers {
...
Вы можете видеть, что TestContainers имеет мета-аннотацию @ExtendWith(TestcontainersExtension.class), что делает его расширением Юпитера.
@TestContainers в сочетании со Spring работают медленно
При использовании @TestContainers для каждого метода тестирования создается новый тестовый контейнер. Это медленно. Кроме того, при использовании Spring Boot это также означает, что контекст приложения необходимо перезапустить. Это делает работу еще медленнее.
В идеале вы повторно используете контекст приложения и тестовый контейнер для всех тестов. Это можно сделать, запустив тестовый контейнер через ContextCustomizerFactory и обеспечив уникальность создаваемых вами тестовых данных.
Примечание. Хотя вы можете использовать .withReuse(true) для повторного использования тестового контейнера, это означает, что контейнер не привязан к конкретному контексту приложения. В результате детали одного контекста приложения (из совершенно другого проекта) могут просочиться в другой.
Вы в точку! Контейнер не запускается, поскольку аннотации @TestContainers и @Container принадлежат junit-jupiter-engine. Cucumber использует junit-platform-suite-engine (здесь список тестовых движков JUnit 5).
Мне удалось заставить это работать, удалив обе бесполезные аннотации и запустив контейнер программно, как это сделали вы.
Тем не менее, вы все равно можете рассчитывать на то, что аннотация Spring Boot 3 @ServiceConnection автоматически настроит для вас пул соединений.
Н.Б. для читателей:
@BeforeAll и @AfterAll происходят от io.cucumber.java (как уже упоминалось).withReuse(true) разрешить нескольким приложениям использовать один и тот же контейнер, потому что я запускаю тесты в конвейере, где каждый шаг выполняется в своем собственном контейнере.Вот как выглядят мои классы конфигурации:
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.PostgreSQLContainer;
@CucumberContextConfiguration
@SpringBootTest
public class CucumberSpringConfiguration {
@ServiceConnection
static final PostgreSQLContainer<?> postgreSQLContainer =
new PostgreSQLContainer<>("postgres:16.2");
@BeforeAll
public static void setup() {
postgreSQLContainer.withReuse(true).start();
}
@AfterAll
public static void tearDown() {
postgreSQLContainer.stop();
}
}
и
import static io.cucumber.core.options.Constants.FILTER_TAGS_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.PLUGIN_PROPERTY_NAME;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectClasspathResource("features")
@SelectPackages({"package-with-step-definitions"})
@ConfigurationParameter(
key = PLUGIN_PROPERTY_NAME,
value =
"pretty,\n"
+ "html:target/AcceptanceTestReports/report.html,\n" //
+ "json:target/AcceptanceTestReports/report.json,\n" //
+ "junit:target/AcceptanceTestReports/report.xml")
Аннотация @SelectPackages вам ничего не даст. Он выбирает файлы функций в пакете, в котором их нет.
Cucumber на самом деле не использует Suite Engine. Однако, если вы хотите использовать Cucumber с JUnit и Maven или Gradle, вам придется использовать Suite Engine, поскольку Maven и Gradle в любом случае не могут выбирать тесты, которых нет в файле класса. Это обходной путь.