Тестувати з використанням реальної бази даних життєво необхідно в складних додатках. Уяви, що ти розробляєш додаток, а на продакшені використовуєш 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 з 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() {
// Твій тест тут
}
}
Що тут відбувається?
- Анотація
@Testcontainers: вказує, що ми використовуємо Testcontainers. - Анотація
@Container: автоматично керує життєвим циклом контейнера (запуск/зупинка). @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.
Практичне застосування
- Реальне середовище тестування: ти можеш бути впевнений, що твій додаток працюватиме так само в продакшені, як і в тестах.
- Автоматизація CI/CD: включивши Testcontainers у пайплайни (наприклад, GitHub Actions або GitLab CI), ти зробиш інтеграційні тести більш надійними.
- Динамічне тестування різних конфігурацій: просто змінюй параметри контейнера (наприклад, версію Postgres) і перевіряй, що все працює коректно.
Testcontainers — це потужний інструмент для інтеграційного тестування з реальними базами даних, який робить твій тестовий процес більш надійним і наближеним до реальної експлуатації. Вчися його використовувати, і не забувай, що контейнери після тестів все ж потрібно вимикати... Добре, що Testcontainers робить це автоматично! 😉
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ