Тесты на настоящей PostgreSQL: Testcontainers
Все тесты до сих пор шли на H2 в памяти, а на сервере работает PostgreSQL. Базы похожи, но не одинаковы: родной SQL-запрос, тип столбца или миграция могут пройти на H2 и сломаться на PostgreSQL. Testcontainers — библиотека, которая перед тестом запускает настоящую базу в контейнере Docker, а после теста удаляет его. Устанавливать PostgreSQL на компьютер не нужно, но нужен работающий Docker (на Windows и macOS — Docker Desktop).
Зависимости
На start.spring.io это модули Testcontainers и PostgreSQL Driver. В pom.xml появятся три тестовые зависимости:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<scope>test</scope>
</dependency>Версии указывать не нужно — их задаёт Spring Boot (в Boot 4.1 это Testcontainers 2). Кроме них нужны драйвер org.postgresql:postgresql и модуль flyway-database-postgresql — те же, что в уроках о подключении базы и о Flyway. Если вы их ещё не добавили, добавьте сейчас: на сервере они всё равно понадобятся.
Контейнер как бин
Генератор кладёт в тесты такой класс, только с образом postgres:latest. Лучше указать ту же версию, что на сервере:
// src/test/java/com/example/blog/TestcontainersConfiguration.java
package com.example.blog;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.postgresql.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
@Bean
@ServiceConnection
PostgreSQLContainer postgresContainer() {
return new PostgreSQLContainer(DockerImageName.parse("postgres:18"));
}
}Контейнер здесь — обычный бин Spring: он запускается вместе с тестовым контекстом и останавливается после всех тестов. @ServiceConnection — главное в уроке: Spring Boot сам узнаёт у контейнера адрес, порт, имя пользователя и пароль и подключает к ним DataSource и Flyway. Писать spring.datasource.url не нужно, адрес из application.yaml будет заменён.
Тест
// src/test/java/com/example/blog/PostgresTests.java
package com.example.blog;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Instant;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
@Import(TestcontainersConfiguration.class)
@Transactional
class PostgresTests {
@Autowired
private PostRepository posts;
@Autowired
private UserRepository users;
@Test
void searchWorksOnPostgres() {
User anna = users.save(new User("anna", "{noop}changeme"));
posts.save(new Post("Привет, PostgreSQL", "Текст", anna, Instant.now()));
assertThat(posts.search("postgresql")).hasSize(1);
assertThat(posts.findLatest(5)).hasSize(1);
}
}@Import подключает конфигурацию с контейнером. Профиль test с H2 здесь не включаем. Тест проверяет то, что зависит от базы: миграции Flyway, поиск без учёта регистра и родной SQL с limit.
Запуск
./mvnw test -Dtest=PostgresTestsПри первом запуске Docker скачает образ postgres:18 — это займёт минуту. В журнале теста появятся строки Testcontainers о запуске контейнера, затем обычный результат JUnit. Остальные тесты по-прежнему идут на H2 и не требуют Docker.
Приложение с базой в контейнере
Генератор создаёт ещё один класс:
// src/test/java/com/example/blog/TestBlogApplication.java
package com.example.blog;
import org.springframework.boot.SpringApplication;
public class TestBlogApplication {
public static void main(String[] args) {
SpringApplication.from(BlogApplication::main).with(TestcontainersConfiguration.class).run(args);
}
}Он запускает всё приложение с той же PostgreSQL в контейнере: ./mvnw spring-boot:test-run. Удобно, чтобы проверить блог в браузере на настоящей базе, не устанавливая её.
Частая ошибка
Docker не установлен или не запущен. Тест падает ещё до первой строки:
ERROR ... DockerClientProviderStrategy : Could not find a valid Docker environment. Please check configuration.
Could not find a valid Docker environment for Testcontainers.
- Ensure a Docker-compatible container engine is installed and running.
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0 <<< FAILURE! -- in com.example.blog.PostgresTests
Caused by: java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configurationЗапустите Docker Desktop и дождитесь, пока он полностью загрузится, затем повторите команду. Проверить Docker можно отдельно: docker info должна вывести сведения о сервере, а не ошибку.