JavaRush /Курси /Модуль 5. Spring /Лекція 140: Використання Testcontainers для тестування з ...

Лекція 140: Використання Testcontainers для тестування з реальною базою даних

Модуль 5. Spring
Рівень 9 , Лекція 9
Відкрита

Тестувати з використанням реальної бази даних життєво необхідно в складних додатках. Уяви, що ти розробляєш додаток, а на продакшені використовуєш PostgreSQL. Ти пишеш тести, використовуючи H2, і все чудово працює… до першої зустрічі з реальним світом, де схема даних очікує непередбачений крах. Саме тут на допомогу приходить Testcontainers.

Testcontainers — це бібліотека Java, яка дозволяє зручно запускати ізольовані Docker-контейнери для використання в тестах. Вона дає можливість підняти реальну базу даних (PostgreSQL, MySQL, MongoDB та інші) в контейнері, протестувати додаток і автоматично знищити контейнер після завершення тесту. Ніяких «сюрпризів» у вигляді залишків тестових артефактів на твоїй локальній машині.


Переваги Testcontainers

  • Реальна поведінка: використання тієї ж бази даних, що й на продакшені, мінімізує ймовірність неприємних сюрпризів.
  • Одне й те саме оточення всюди: контейнери гарантують однакове оточення для тестів на локалці, CI/CD і навіть у твоєму холодильнику, якщо він підтримує Docker.
  • Чистота: після тесту контейнер знищується, залишаючи систему неушкодженою.
  • Підтримка багатьох баз даних: PostgreSQL, MySQL, MariaDB, MongoDB, Cassandra і навіть Kafka.

Встановлення Testcontainers

Залежності Maven/Gradle

Для початку додамо Testcontainers у проект. Використаємо PostgreSQL як приклад бази даних.

Maven:


<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.0</version>
    <scope>test</scope>
</dependency>

Gradle:


testImplementation("org.testcontainers:postgresql:1.19.0")
Важливо:
Щоб Testcontainers працював, потрібно, щоб Docker був встановлений і запущений на твоїй машині.

Проста налаштування Testcontainers з PostgreSQL

Почнемо зі створення базового тесту, який використовує контейнер PostgreSQL.

Крок 1: Підготовка тестового контейнера

Створимо клас з тестами, де ми будемо використовувати контейнер. Testcontainers дозволяє налаштовувати контейнери для PostgreSQL таким чином:


import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;

public class PostgresContainerTest {

    @Test
    void testPostgresContainer() {
        try (PostgreSQLContainer
    postgres = new PostgreSQLContainer<>("postgres:15")) {
            // Запускаємо контейнер
            postgres.start();

            // Виводимо дані контейнера для перевірки
            System.out.println("Postgres URL: " + postgres.getJdbcUrl());
            System.out.println("Username: " + postgres.getUsername());
            System.out.println("Password: " + postgres.getPassword());

            // Тут можна підключитися до бази через JDBC і виконати тестові дії
        }
    }
}

Цей тест піднімає контейнер з Postgres версії 15. Після виклику start() контейнер піднімається, і Testcontainers автоматично знаходить доступний порт для бази. Після завершення блоку try контейнер зупиняється.

Крок 2: Інтеграція з Spring Boot

Зробимо так, щоб Spring Boot використовував цей контейнер у тестах. Налаштування робиться через @DynamicPropertySource.


import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers
class DatabaseIntegrationTest {

    @Container
    private static final PostgreSQLContainer
    POSTGRES = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void overrideProperties(org.springframework.test.context.DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRES::getUsername);
        registry.add("spring.datasource.password", POSTGRES::getPassword);
    }

    @Test
    void contextLoads() {
        // Твій тест тут
    }
}

Що тут відбувається?

  1. Анотація @Testcontainers: вказує, що ми використовуємо Testcontainers.
  2. Анотація @Container: автоматично керує життєвим циклом контейнера (запуск/зупинка).
  3. @DynamicPropertySource: перевизначає властивості spring.datasource, щоб Spring використовував параметри контейнера.

Написання тесту для репозиторію

Додамо тест для репозиторію, в якому перевіримо збереження сутності в базу.

Сутність User:


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    // Геттери та сеттери
}

Репозиторій:


import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

Тест:


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit.jupiter.Testcontainers;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@Testcontainers
class UserRepositoryTest {

    @Container
    private static final PostgreSQLContainer
    POSTGRES = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @DynamicPropertySource
    static void overrideProperties(org.springframework.test.context.DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRES::getUsername);
        registry.add("spring.datasource.password", POSTGRES::getPassword);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void testSaveUser() {
        User user = new User();
        user.setName("John Doe");

        User savedUser = userRepository.save(user);
        assertThat(savedUser.getId()).isNotNull();
        assertThat(savedUser.getName()).isEqualTo("John Doe");
    }
}

Цей тест піднімає контейнер бази даних, підключає його до Spring Boot і дозволяє протестувати збереження сутності без потреби вручну піднімати Postgres.


Особливості та помилки

  • Docker має бути встановлений і запущений: якщо Docker не працює, твої тести просто відмовляться запускатися.
  • Порт 5432 вже зайнятий? Testcontainers автоматично використовує довільний порт для контейнера, тому конфлікти виключені.
  • Довга ініціалізація? перший запуск може зайняти більше часу через завантаження образу з Docker Hub.

Практичне застосування

  1. Реальне середовище тестування: ти можеш бути впевнений, що твій додаток працюватиме так само в продакшені, як і в тестах.
  2. Автоматизація CI/CD: включивши Testcontainers у пайплайни (наприклад, GitHub Actions або GitLab CI), ти зробиш інтеграційні тести більш надійними.
  3. Динамічне тестування різних конфігурацій: просто змінюй параметри контейнера (наприклад, версію Postgres) і перевіряй, що все працює коректно.

Testcontainers — це потужний інструмент для інтеграційного тестування з реальними базами даних, який робить твій тестовий процес більш надійним і наближеним до реальної експлуатації. Вчися його використовувати, і не забувай, що контейнери після тестів все ж потрібно вимикати... Добре, що Testcontainers робить це автоматично! 😉

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ