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-memory | Testcontainers + 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):
- JUnit находит класс
NoteRepositoryIT, видит@Testcontainers. - Статическое поле
postgresс@Container: Docker скачивает образpostgres:16-alpine(первый раз — долго), стартует контейнер. - Метод
configureрегистрирует URL, логин и пароль контейнера в Spring Environment. @DataJpaTestподнимает контекст сNoteRepository, Hibernate создаёт таблицу (ddl-auto=create-dropв test resources).- Тест
saveAndLoad:save→ INSERT,findById→ SELECT из реального Postgres. - После класса контейнер останавливается; данные не остаются на диске хоста.
Разбор ключевых строк кода:
| Строка | Зачем |
|---|---|
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 environment | Docker не запущен |
| Тесты висят | Образ 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
Что попробовать
- Добавить Flyway-миграцию и убедиться, что тест проходит на чистой БД в контейнере.
- В CI (GitHub Actions) — сервис
postgresилиdocker+ Testcontainers — см. 294.md. - Связать с Spring Security:
@SpringBootTest+MockMvc+ Testcontainers.
Дальше
Spring Security · JVM — диагностика · DevOps
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Основы Java - устройство JDK/JVM, модель компиляции и базовые принципы платформонезависимого выполнения. Java — это объектно-ориентированный язык программирования общего назначения, который работает на принципах «напиши один раз, запускай в любом месте». Набор советов, правил, принципов и обычаев в разработке на этом языке. История Java — от проекта Green и Oak до OpenJDK, LTS-релизов и современной платформы (модули, records, виртуальные потоки). Библиотеки, фреймворки, инструменты сборки, тестирования, развёртывания и мониторинга. Что такое пакет и пакетная структура, как собираются проекты на Java. Справочник-шпаргалка по конфигурациям в Java — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Гайд по установке и настройке с написанием первой программы и её запуском. Практические примеры — консольные утилиты, композиция классов в мини-игре и первое Swing-приложение. Точки останова, пошаговое выполнение, панели Variables и Call Stack — практика отладки в IntelliJ IDEA. Кавычки, точки, запятые, скобки и прочие знаки препинания. Это полный справочник всех ключевых слов языка Java, включая основные, контекстные и зарезервированные слова.Основы языка Java
Что требуется знать перед началом изучения языка программирования Java
Рекомендации по разработке на Java
История языка Java
Экосистема Java-приложений
Структура и сборки Java-проектов
Справочник по конфигурациям в Java
Первая программа на Java
Простые приложения на Java
Отладка Java-кода в IDE
Синтаксис и пунктуация в Java
Ключевые слова в Java