Перейти к основному содержимому

Testcontainers — интеграционные тесты с реальной БД

Разработчику Архитектору

Testcontainers — интеграционные тесты с реальной БД

Юнит-тест с @MockBean на репозитории проверяет ваш код в отрыве от БД. Интеграционный тест поднимает Spring (или слой JPA) и ходит в настоящую базу — так вы ловите ошибки SQL, типов колонок и миграций.

H2 в памяти удобна для старта: быстро, без Docker. В проде часто PostgreSQL — другой диалект, другие типы индексов. Testcontainers запускает контейнер postgres:16 только на время теста, затем останавливает.

Нужно: установленный и запущенный Docker Desktop (Windows/macOS) или docker engine (Linux). Без Docker тест упадёт с Could not find Docker environment.

База: JPA — практический старт · JUnit 5.

Словарь

ТерминОбъяснение
TestcontainersБиблиотека: из Java вызывается Docker API, поднимается образ (PostgreSQL, Redis, …)
КонтейнерИзолированный процесс с БД; порт на хосте случайный, URL выдаёт библиотека
@ContainerПоле с контейнером; JUnit 5 стартует его перед классом тестов
@DataJpaTestПоднимает только JPA (репозитории, EntityManager), без полного веб-слоя — быстрее
@DynamicPropertySourceПодставляет spring.datasource.url во время теста, не трогая прод-application.properties
Replace.NONE«Не подменяй datasource на встроенную H2» — иначе тест думает, что работает с H2
JDBC URLСтрока подключения вида jdbc:postgresql://localhost:32769/test

Testcontainers - PostgreSQL в Docker вместо H2 in-memory

H2 in-memoryTestcontainers + PostgreSQL
Быстро, без DockerБлиже к продакшену
Другой диалект SQLТе же типы, индексы, расширения
Хорошо для обучения JPAХорошо для «интеграционного» слоя

Что получится

Один тест: сохранить Note через NoteRepository, прочитать из БД в контейнере — без @MockBean на репозиторий.


Зависимости (Maven)

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>

В pom.xml удобно зафиксировать BOM Testcontainers (версию возьмите с testcontainers.com):

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.19.8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Сущность и репозиторий

Используйте те же Note / NoteRepository из 293.md, либо минимальный вариант:

@Entity
class Note {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String text;
protected Note() {}
Note(String text) { this.text = text; }
Long getId() { return id; }
String getText() { return text; }
}

interface NoteRepository extends JpaRepository<Note, Long> {}

Тест с @Testcontainers

package com.example.notes;

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

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

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class NoteRepositoryIT {

@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

@DynamicPropertySource
static void configure(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}

@Autowired NoteRepository repo;

@Test
void saveAndLoad() {
Note saved = repo.save(new Note("testcontainers"));
assertThat(saved.getId()).isNotNull();

Note loaded = repo.findById(saved.getId()).orElseThrow();
assertThat(loaded.getText()).isEqualTo("testcontainers");
}
}

Разбор по шагам (что происходит при mvn test):

  1. JUnit находит класс NoteRepositoryIT, видит @Testcontainers.
  2. Статическое поле postgres с @Container: Docker скачивает образ postgres:16-alpine (первый раз — долго), стартует контейнер.
  3. Метод configure регистрирует URL, логин и пароль контейнера в Spring Environment.
  4. @DataJpaTest поднимает контекст с NoteRepository, Hibernate создаёт таблицу (ddl-auto=create-drop в test resources).
  5. Тест saveAndLoad: save → INSERT, findById → SELECT из реального Postgres.
  6. После класса контейнер останавливается; данные не остаются на диске хоста.

Разбор ключевых строк кода:

СтрокаЗачем
new PostgreSQLContainer<>("postgres:16-alpine")Какой образ Docker использовать
postgres::getJdbcUrlЛенивая ссылка на URL после старта контейнера
repo.save(new Note(...))Проверяем цепочку entity → SQL → чтение обратно
assertThat(loaded.getText())AssertJ — читаемые проверки вместо assertEquals

Отличие от @MockBean на репозиторий: мок вернёт то, что вы сами задали в when(...); интеграционный тест проверяет, что Hibernate и PostgreSQL согласованы.


Полный контекст Spring Boot (по желанию)

Если нужен @SpringBootTest с REST:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class NotesApiIT {
// тот же @Container и @DynamicPropertySource
}

Частые ошибки

СимптомПричина
Could not find Docker environmentDocker не запущен
Тесты висятОбраз postgres качается впервые — подождите
schema not foundНет ddl-auto=create / Flyway в тестовом профиле
H2 вместо PostgresНе указали Replace.NONE

src/test/resources/application.properties для IT:

spring.jpa.hibernate.ddl-auto=create-drop

Что попробовать

  1. Добавить Flyway-миграцию и убедиться, что тест проходит на чистой БД в контейнере.
  2. В CI (GitHub Actions) — сервис postgres или docker + Testcontainers — см. 294.md.
  3. Связать с Spring Security: @SpringBootTest + MockMvc + Testcontainers.

Дальше

Spring Security · JVM — диагностика · DevOps


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).