@Testcontainers Junit 5 не работает с огурцом и Spring Boot 3

Я написал тест на огурец и заметил, что @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 {
}

В чем может быть проблема?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
2
0
118
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

@Testcontainers — это расширение JUnit Jupiter.

1.1. Что такое Юнит 5?

В отличие от предыдущих версий 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 автоматически настроит для вас пул соединений.

Н.Б. для читателей:

  1. @BeforeAll и @AfterAll происходят от io.cucumber.java (как уже упоминалось)
  2. У меня нет проблемы .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")

Cucumber на самом деле не использует Suite Engine. Однако, если вы хотите использовать Cucumber с JUnit и Maven или Gradle, вам придется использовать Suite Engine, поскольку Maven и Gradle в любом случае не могут выбирать тесты, которых нет в файле класса. Это обходной путь.

M.P. Korstanje 21.06.2024 18:11

Аннотация @SelectPackages вам ничего не даст. Он выбирает файлы функций в пакете, в котором их нет.

M.P. Korstanje 21.06.2024 18:15

Другие вопросы по теме

Похожие вопросы

Как я могу проверить, регистрируется ли мое приложение ожидаемым образом с помощью log4j2?
Предоставление одного ресурса пути к классам для веб-доступа в Spring Boot
Операции сохранения не фиксируются при использовании аннотации @Scheduled
Как исправить эту ошибку: Не удалось выполнить автоматическое подключение. Существует более одного bean-компонента типа UserBusService
Образ Docker с Spring Boot подключается к внешнему Postgres
Spring Security + Keycloak (с самоподписанными сертификатами) – как отключить проверку имени хоста?
Ошибка конфигурации безопасности при обновлении версии приложения весенней загрузки с 2.x.x до 3.3.0
Spring 3 использует сопоставитель объектов для преобразования значения (десериализации) из List<Map<String,Object> в некоторый DTO выдает ошибку для поля Instant
Как я могу распечатать наносекунды в временной метке журнала при входе в Spring Boot 3.3 и Java 17, используя logback или log4j?
Можно ли разместить плагин apm-agent-java внутри JAR-файла приложения?