Модульные тесты проверяют логику в изоляции, но не говорят, правильно ли приложение работает с базой данных, кешем или внешними сервисами. Для этого нужны интеграционные тесты — с реальными зависимостями.
Проблема H2 и моков
Самый простой способ протестировать репозиторий — подключить встроенную базу данных H2. Это быстро, не требует Docker, тесты запускаются везде. Но у подхода есть серьёзный изъян: H2 — не PostgreSQL.
Различия накапливаются незаметно: синтаксис SQL-функций, поведение при конфликтах уникальности, работа JSON-типов, оконные функции. Тест проходит на H2, а на продакшне падает — потому что диалекты отличаются.
То же с моками базы данных: мок проверяет, что метод был вызван с нужными аргументами, но не проверяет сам SQL-запрос, маппинг результата и поведение транзакции. Интеграционный тест поднимает тот же PostgreSQL, что работает в продакшне, и убирает целый класс ошибок.
@SpringBootTest: полный контекст
@SpringBootTest поднимает весь контекст приложения — точно так же, как при запуске. Это наиболее тяжёлый вариант теста, зато самый близкий к реальной работе.
@SpringBootTest
@AutoConfigureMockMvc
class OrderApiTest {
@Autowired
MockMvc mockMvc;
@Test
void createsOrder() throws Exception {
mockMvc.perform(post("/orders")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"productId": "abc", "quantity": 2}
"""))
.andExpect(status().isCreated());
}
}
По умолчанию сервер не стартует — вместо этого используется MockMvc. Если нужен реальный HTTP-порт, добавьте webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT.
Полный контекст оправдан, когда важно проверить сквозной путь: HTTP → сервис → база данных → ответ. Для изолированной проверки одного слоя есть срезы.
Срезы: @DataJpaTest и @WebMvcTest
Срез (slice) — это урезанный контекст Spring, в котором поднимается только нужный слой. Остальные бины не загружаются, поэтому срез стартует значительно быстрее полного контекста.
@DataJpaTest поднимает только слой работы с данными: репозитории, EntityManager, транзакции. По умолчанию он подключает встроенную базу — но вместо H2 можно подключить Testcontainers (об этом ниже).
@DataJpaTest
class ProductRepositoryTest {
@Autowired
ProductRepository repository;
@Test
void findsActiveProducts() {
var saved = repository.save(new Product("Widget", true));
var found = repository.findAllActive();
assertThat(found).contains(saved);
}
}
@WebMvcTest поднимает только слой контроллеров: @Controller, @ControllerAdvice, фильтры, MockMvc. Сервисы и репозитории в этот контекст не входят — их нужно мокировать через @MockitoBean.
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
MockMvc mockMvc;
@MockitoBean
OrderService orderService;
@Test
void returnsBadRequestOnMissingBody() throws Exception {
mockMvc.perform(post("/orders"))
.andExpect(status().isBadRequest());
}
}
Testcontainers: реальный PostgreSQL в Docker
Testcontainers — это Java-библиотека, которая запускает Docker-контейнеры прямо из кода теста. Контейнер стартует перед тестом, останавливается после. Никакой настройки окружения вручную — достаточно установленного Docker.
<!-- build.gradle или pom.xml -->
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation 'org.testcontainers:postgresql'
@SpringBootTest
@Testcontainers
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
OrderRepository repository;
@Test
void savesAndReadsOrder() {
var order = repository.save(new Order(UUID.randomUUID(), "PENDING"));
assertThat(repository.findById(order.id())).isPresent();
}
}
@Container + static означает, что контейнер создаётся один раз на класс. @DynamicPropertySource передаёт URL, логин и пароль контейнера в контекст Spring до его старта.
@ServiceConnection: без ручной настройки URL
Spring Boot 3.1 ввёл @ServiceConnection — аннотацию, которая убирает шаблонный @DynamicPropertySource. Spring сам распознаёт тип контейнера и настраивает нужные свойства.
@SpringBootTest
@Testcontainers
class OrderRepositoryTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
// @DynamicPropertySource больше не нужен
@Autowired
OrderRepository repository;
}
Короткая формула: @ServiceConnection = автоматическая настройка datasource, Redis, RabbitMQ и других поддерживаемых типов контейнеров.
Для Redis это работает точно так же:
@Container
@ServiceConnection
static GenericContainer<?> redis =
new GenericContainer<>("redis:7-alpine").withExposedPorts(6379);
Когда полный контекст, а когда срез
| Ситуация | Что использовать |
|---|---|
| Сквозной тест HTTP → база → ответ | @SpringBootTest + Testcontainers |
| Проверить SQL-запросы репозитория | @DataJpaTest + @ServiceConnection |
| Проверить валидацию и ответы контроллера | @WebMvcTest + @MockitoBean |
| Проверить бизнес-логику сервиса | Модульный тест, без Spring-контекста |
Правило: поднимать ровно столько контекста, сколько нужно. Лишние бины замедляют старт и могут вносить неожиданные зависимости. Полный @SpringBootTest оправдан только для сквозных сценариев.
Если тестов с Testcontainers много, контейнер стоит выносить в общий базовый класс с @Container static — тогда Docker-образ поднимается один раз для всего набора тестов, а не отдельно под каждый класс.
Коротко
- H2 и моки баз данных скрывают ошибки, которые видны только с реальным PostgreSQL.
@SpringBootTestподнимает весь контекст; срезы@DataJpaTestи@WebMvcTest— только нужный слой.- Testcontainers запускает Docker-контейнер прямо из теста — тот же образ, что в продакшне.
@ServiceConnection(Spring Boot 3.1+) убирает@DynamicPropertySourceи сам настраивает datasource.- Выбирайте минимальный контекст: срез быстрее полного
@SpringBootTest. - Общий базовый класс с
staticконтейнером сокращает время сборки при большом числе тестов.
Что почитать дальше
- Пирамида тестирования — как соотносятся модульные, интеграционные и end-to-end тесты.
- Моки и внешние зависимости — когда мок уместен, а когда лучше реальный контейнер.
- Тесты в Spring —
@SpringBootTestи срезы подробнее в контексте Spring-экосистемы. - Стандарты тестирования — стайл-гайд по структуре и именованию тестов.