Testcontainers и JUnit 5: проверяем PostgreSQL из Java
Автор: Казачкин Даниил Михайлович · Обновлено
Интеграционный тест SQL должен проверять реальную семантику выбранной базы. Testcontainers запускает временный PostgreSQL для теста, отдаёт фактический JDBC URL и удаляет…
Интеграционный тест SQL должен проверять реальную семантику выбранной базы. Testcontainers запускает временный PostgreSQL для теста, отдаёт фактический JDBC URL и удаляет окружение после завершения. Это позволяет не зависеть от базы на localhost:5432 и не рисковать данными разработчика.
Полный Maven-проект
Нужны JDK 17 или новее, Maven 3.9 и доступный Docker Engine. Проверяйте Docker из того же окружения, где запускается Maven: docker version. Создайте новую папку и файл pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>postgres-container-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<version>2.0.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<version>2.0.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version>
</plugin>
</plugins>
</build>
</project>Версии закреплены для воспроизводимого примера, проверенного в сентябре 2026 года. У Testcontainers 2.x новые имена модулей и PostgreSQLContainer находится в пакете org.testcontainers.postgresql. Старые примеры с другими artifactId нельзя смешивать с этим POM. JDBC driver добавлен явно: модуль контейнера не обязан предоставлять его приложению. Зависимости PostgreSQL-модуля.
Тест с данными
Сохраните src/test/java/example/PostgresContainerTest.java:
package example;
import java.sql.DriverManager;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Testcontainers
class PostgresContainerTest {
@Container
static final PostgreSQLContainer db =
new PostgreSQLContainer("postgres:17-alpine")
.withDatabaseName("lesson")
.withUsername("lesson")
.withPassword("training-only");
@Test
void savesAndReadsNote() throws Exception {
try (var connection = DriverManager.getConnection(
db.getJdbcUrl(), db.getUsername(), db.getPassword());
var statement = connection.createStatement()) {
statement.execute("CREATE TABLE note (id integer PRIMARY KEY, body text NOT NULL)");
try (var insert = connection.prepareStatement(
"INSERT INTO note (id, body) VALUES (?, ?)")) {
insert.setInt(1, 7);
insert.setString(2, "container-ready");
assertEquals(1, insert.executeUpdate());
}
try (var rows = statement.executeQuery("SELECT body FROM note WHERE id = 7")) {
assertTrue(rows.next());
assertEquals("container-ready", rows.getString("body"));
}
}
}
}Запуск и критерий успеха
В корне выполните mvn test. Первый запуск скачивает зависимости и служебные образы. Ожидайте один пройденный тест и BUILD SUCCESS. Логи и отчёт находятся в target/surefire-reports. Тест не подключается к произвольной существующей базе: параметры получены у созданного контейнера.
Testcontainers выбирает доступный внешний порт. Не подставляйте 5432 вручную: db.getJdbcUrl() включает getHost() и mapped port. Состояние running ещё не означает готовность базы; PostgreSQL-модуль использует свою стратегию ожидания. Не заменяйте её фиксированным Thread.sleep(5000). Жизненный цикл и подключение.
Что разделяют тесты
Статическое поле @Container используется на уровне класса; база может сохранять состояние между методами этого класса. Обычное нестатическое поле создаёт контейнер для каждого тестового метода. Для нескольких тестов выберите явную изоляцию: отдельные схемы, контролируемую очистку или откат транзакции, если архитектура действительно позволяет его. Порядок методов нельзя использовать как скрытое условие корректности.
Диагностика и упражнение
Could not find a valid Docker environment возникает до SQL: сравните окружение IDE и shell, endpoint Docker, доступ к сокету и версии клиента. Подробная развилка — в [уроке о среде Testcontainers](/lessons/without-university/docker-containerization/docker-developer-33). Не открывайте Docker TCP-порт без защиты и не отключайте уборку ресурсов, чтобы спрятать симптом.
Добавьте проверку нарушения PRIMARY KEY отдельным тестом и обеспечьте независимое начальное состояние. Затем выполните весь класс дважды: оба запуска должны проходить без ручной очистки локального PostgreSQL. Это проверка воспроизводимости и жизненного цикла, а не только успешного SELECT.