Использование Testcontainers с расширениями JUnit5

Я использовал Spring boot и JUnit5. Мое приложение работает с несколькими базами данных: MySQL, Clickhouse и т. Д.

Для интеграционного тестирования я создал расширения JUnit5:

public class ClickHouseTestContainersExtension implements BeforeAllCallback, BeforeTestExecutionCallback {

    public static final String IMAGE_NAME = "registry.mycomp.com/db/clickhouse-server:20.5.3.27";

    @Container
    private static final FixedHostPortGenericContainer<?> clickHouse = new FixedHostPortGenericContainer<>(IMAGE_NAME)
            .withCopyFileToContainer(MountableFile.forClasspathResource("dbInitScripts/init_clickhouse.sql"), "/docker-entrypoint-initdb.d/init_clickhouse.sql")
            .withMinimumRunningDuration(Duration.ofSeconds(7))
            .withFixedExposedPort(8124, 8123);

    @Override
    public void beforeAll(ExtensionContext extensionContext) {
        startContainerIfNeed();
    }

    @Override
    public void beforeTestExecution(ExtensionContext extensionContext) {
        startContainerIfNeed();
    }

    public void startContainerIfNeed() {
        if (!clickHouse.isRunning()) {
            log.info("ClickHouse container is not running! Started.....");
            clickHouse.start();
        }
    }
}

И MySQL:

public class MySQLTestContainersExtension implements BeforeAllCallback, BeforeTestExecutionCallback {

    private static final String IMAGE_NAME = "registry.mycomp.com/db/mariadb:10.4.11";

    @Container
    private static final FixedHostPortGenericContainer<?> mariaDb = new FixedHostPortGenericContainer<>(IMAGE_NAME)
            .waitingFor(new LogMessageWaitStrategy()
                    .withRegEx(".*ready for connections.*")
                    .withTimes(2)
                    .withStartupTimeout(Duration.of(3, MINUTES)))
            .withEnv("MYSQL_DATABASE", "db")
            .withEnv("MYSQL_USER", "sys")
            .withEnv("MYSQL_PASSWORD", "qwerty")
            .withEnv("MYSQL_ROOT_PASSWORD", "toor")
            .withEnv("MYSQL_RANDOM_ROOT_PASSWORD", "no")
            .withEnv("MYSQL_ALLOW_EMPTY_PASSWORD", "no")
            .withFixedExposedPort(33060, 3306)
            .withCopyFileToContainer(MountableFile.forClasspathResource("dbInitScripts/init_mysql.sql"),
                    "/docker-entrypoint-initdb.d/init_mysql.sql");

    @Override
    public void beforeAll(ExtensionContext extensionContext) {
        startContainerIfNeed();
    }

    @Override
    public void beforeTestExecution(ExtensionContext extensionContext) {
        startContainerIfNeed();
    }

    public void startContainerIfNeed() {
        if (!mariaDb.isRunning()) {
            log.info("MariaDB container is not running! Started.....");
            mariaDb.start();
        }
    }
}

И ряд других подобных расширений.

Еще у меня есть аннотации:

@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RUNTIME)
@ExtendWith({MySQLTestContainersExtension.class})
public @interface MySQL {
}

@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RUNTIME)
@ExtendWith({ClickHouseTestContainersExtension.class})
public @interface ClickHouse {
}

@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RUNTIME)
@ExtendWith({ClickHouseTestContainersExtension.class, MySQLTestContainersExtension.class,
        PostgreSQLTestContainersExtension.class, IgniteTestContainersExtension.class})
public @interface WithAllDatabases {
}

И, когда я пишу какой-нибудь тест, я использую это расширение:


    @Test
    @ClickHouse
    @DisplayName("Test connection ClickHouse with alive status")
    void testConnectionClickHouseWithAliveStatus() throws IOException {
        //smth
    }

    @Test
    @MySQL
    @DisplayName("Test connection MySQL with alive status")
    void testConnectionMariaDBWithAliveStatus() throws IOException {
        //smth
    }

  //etc

Свойства моих подключений, такие как имя пользователя, пароль, URL-адрес, хранятся в базе данных, а не в apllication.yml. Любой пользователь моей системы может вручную добавить новое соединение.

Так что я не могу просто использовать пружинный механизм, такой как @DynamicPropertySource.

Более того, я никак не могу получить сгенерированный случайный порт из расширения!

Поэтому я использую фиксированный порт для создания набора тестовых подключений к базе данных, созданной в содержимом тестов!

Подскажите, есть ли какой-нибудь подход, который позволит вам использовать расширение JUnit 5 и использовать случайный порт. Моей задачей было поднять любую базу данных для конкретного теста, просто поместив аннотацию над этим тестом.


person All_Safe    schedule 24.11.2020    source источник


Ответы (1)


У меня есть несколько вопросов

  1. Это работает, и вы просто хотите использовать случайные порты вместо фиксированных или не работает вообще?
  2. Думаю, вы слишком строго следуете требованию. Сделать так же просто, как разместить одну аннотацию в тестовом классе / методе, не является конечной целью, поскольку у вас нет API для работы с вашим кодом. И вы не можете получить доступ ни к чему (полям, методам) внутри класса расширения, насколько я понимаю. Я считаю, что вам может быть удобнее:
  • иметь одноэлементную фабрику для каждого контейнера базы данных (превратить startContainerIfNeed() в фабричный метод) и вызывать MySQLTestContainersExtension.getContainer(), когда вам нужен контейнер
  • или сделать абстрактный класс из классов расширения и расширить его в своих тестовых классах, в этом случае вы можете либо сохранить статическую переменную mariaDb, либо сделать ее обычным полем и получить доступ через геттер

Также @Container ничего не делает в вашем случае, вы можете прочитать об этом здесь

person Vitaly Chura    schedule 25.11.2020