Тесты на Spring бывают быстрыми и медленными, и разница почти всегда в том, сколько контекста вы поднимаете. На собесе спрашивают про слайсы, MockMvc, Testcontainers и кеш контекста.
Уровни
- Юнит-тест без Spring: сервис создаётся через
new, зависимости это моки Mockito. Миллисекунды. Основная масса тестов бизнес-логики. - Слайс-тесты: поднимается только часть контекста.
@SpringBootTest: весь контекст, ближе всего к проду, самый медленный.
Слайсы
| Аннотация | Что поднимает |
|---|---|
@WebMvcTest(OrderController.class) | MVC-слой: контроллеры, ControllerAdvice, конвертеры, фильтры Security, MockMvc. Сервисы нужно замокать |
@DataJpaTest | JPA: сущности, репозитории, 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на тестовом методе?