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

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)

Код ITЗагрузка примера кода…

Разбор:

  • XML-блок через теги dependency`, `groupId`, `artifactId`, `scope описывает конфигурацию декларативно, без ручной Java-инициализации.
  • Смысл фрагмента в том, что сборщик/контейнер читает структуру файла и сам подтягивает нужные зависимости.
  • Ошибка в одном теге или координате сразу ломает весь сценарий, поэтому это критическая часть примера.
  • Такой подход отделяет код приложения от инфраструктурных настроек и упрощает сопровождение.
<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>

Разбор:

  • XML-блок через теги dependencyManagement`, `dependencies`, `dependency`, `groupId`, `artifactId`, `version описывает конфигурацию декларативно, без ручной Java-инициализации.
  • Смысл фрагмента в том, что сборщик/контейнер читает структуру файла и сам подтягивает нужные зависимости.
  • Ошибка в одном теге или координате сразу ломает весь сценарий, поэтому это критическая часть примера.
  • Такой подход отделяет код приложения от инфраструктурных настроек и упрощает сопровождение.
@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> {}

Разбор:

  • В этом фрагменте ключевой объект — Note, плюс NoteRepository; через него показан конкретный кусок архитектуры из раздела.
  • Аннотации @Entity, @Id, @GeneratedValue, @Column включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах GeneratedValue()`, `Column()`, `Note()`, `getId(): они задают путь от входа к результату.
  • По return видно, какой итог выходит наружу: объект домена, HTTP-ответ или конфигурационный результат.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте ключевой объект — NoteRepositoryIT; через него показан конкретный кусок архитектуры из раздела.
  • Аннотации @DataJpaTest, @AutoConfigureTestDatabase, @Testcontainers, @Container, @DynamicPropertySource включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах AutoConfigureTestDatabase()`, `configure()`, `add()`, `saveAndLoad(): они задают путь от входа к результату.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class NotesApiIT {
// тот же @Container и @DynamicPropertySource
}

Разбор:

  • В этом фрагменте ключевой объект — NotesApiIT; через него показан конкретный кусок архитектуры из раздела.
  • Аннотации @SpringBootTest, @Testcontainers, @Container, @DynamicPropertySource включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах SpringBootTest(): они задают путь от входа к результату.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.
spring.jpa.hibernate.ddl-auto=create-drop

Разбор:

  • В .properties здесь важны ключи spring.jpa.hibernate.ddl-auto: именно они управляют поведением приложения при старте.
  • Формат плоский, поэтому каждую строку легко сопоставить с эффектом в логах и поведении endpoint-ов.
  • Этот способ конфигурации удобен для быстрых изменений и отладки без перекомпиляции.
  • Типичный риск — опечатка в ключе: файл валиден, но параметр не читается фреймворком.
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");

@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);

Разбор:

  • Этот фрагмент показывает рабочую Java-логику, где важен не синтаксис сам по себе, а порядок действий и ответственность кода.
  • Аннотации @Container включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах withExposedPorts(): они задают путь от входа к результату.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.