Урок по треку Spring и Spring Boot 7 мин

Тестирование Spring-приложений

@SpringBootTest, слайсы @WebMvcTest и @DataJpaTest, MockMvc, Testcontainers и кеш контекста.

Тесты на Spring бывают быстрыми и медленными, и разница почти всегда в том, сколько контекста вы поднимаете. На собесе спрашивают про слайсы, MockMvc, Testcontainers и кеш контекста.

Уровни

  • Юнит-тест без Spring: сервис создаётся через new, зависимости это моки Mockito. Миллисекунды. Основная масса тестов бизнес-логики.
  • Слайс-тесты: поднимается только часть контекста.
  • @SpringBootTest: весь контекст, ближе всего к проду, самый медленный.

Слайсы

АннотацияЧто поднимает
@WebMvcTest(OrderController.class)MVC-слой: контроллеры, ControllerAdvice, конвертеры, фильтры Security, MockMvc. Сервисы нужно замокать
@DataJpaTestJPA: сущности, репозитории, TestEntityManager; каждый тест в транзакции с откатом; по умолчанию встроенная БД
@JsonTestтолько Jackson
@RestClientTestклиент RestClient или RestTemplate с MockRestServiceServer
@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired MockMvc mvc;
    @MockitoBean OrderService service;      // Spring Boot 3.4+; раньше @MockBean

    @Test
    void returns404ForMissingOrder() throws Exception {
        when(service.get(42)).thenThrow(new OrderNotFoundException(42));
        mvc.perform(get("/api/orders/42"))
           .andExpect(status().isNotFound())
           .andExpect(jsonPath("$.detail").value("Order 42 not found"));
    }
}

@MockBean из Spring Boot помечен устаревшим с Boot 3.4, на смену пришли @MockitoBean и @MockitoSpyBean из Spring Framework 6.2.

Testcontainers вместо H2

@DataJpaTest по умолчанию подменяет DataSource встроенной базой. H2 отличается от PostgreSQL диалектом, типами (jsonb, массивы), блокировками и индексами, поэтому тест может пройти там, где прод упадёт. Правильнее настоящая СУБД в контейнере:

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class OrderRepositoryTest {
    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16-alpine");
}

@ServiceConnection (Boot 3.1+) сам прописывает URL, логин и пароль из контейнера.

Кеш контекста

Spring кеширует созданные контексты между тестовыми классами, если конфигурация совпадает. Ключ кеша включает набор моков (@MockitoBean), профили, свойства (@TestPropertySource), @DirtiesContext. Каждый тестовый класс с уникальным набором моков поднимает новый контекст, и прогон растягивается на минуты. Лечится общими базовыми классами с одинаковой конфигурацией и отказом от лишних @DirtiesContext.

Транзакции в тестах

@Transactional на тесте откатывает изменения после него. Но тогда код проверяется внутри одной транзакции: не срабатывают @TransactionalEventListener(AFTER_COMMIT), не видны проблемы с LazyInitializationException и не выполняется flush, если его не вызвать. Для @SpringBootTest(webEnvironment = RANDOM_PORT) с реальными HTTP-запросами откат вообще не работает: запрос обрабатывается в другом потоке.

Асинхронщина

Не Thread.sleep, а ожидание условия: Awaitility await().atMost(5, SECONDS).until(...). Для Kafka есть spring-kafka-test с @EmbeddedKafka или Testcontainers Kafka.

Частые ошибки

  • @SpringBootTest для проверки одной функции сервиса.
  • H2 вместо настоящей базы.
  • Уникальные @MockitoBean в каждом классе и десятки контекстов.
  • Thread.sleep в тестах асинхронной логики.

Что спросят на собесе

  • Чем @SpringBootTest отличается от @WebMvcTest и @DataJpaTest?
  • Как тестировать контроллер? Что такое MockMvc?
  • Почему H2 в тестах опасен, что такое Testcontainers?
  • Как работает кеш контекста и почему тесты бывают медленными?
  • Что происходит с @Transactional на тестовом методе?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.