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)
Код 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(): они задают путь от входа к результату. - При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.