Как разделить тесты по классам и поднимать Spring контекст один раз?
Что проверяют
Знаете ли вы, что Spring уже кеширует контекст между тестовыми классами, и понимаете ли, что этот кеш ломает. Следом обычно спрашивают, почему в проекте контекст всё равно поднимается десятки раз и как это найти.
Как отвечать
Spring TestContext Framework хранит поднятые ApplicationContext в статическом кеше внутри JVM. Ключ кеша это полная конфигурация теста: конфигурационные классы, профили, свойства, набор подменённых бинов, webEnvironment. Если у двух классов она совпадает, второй получает готовый контекст мгновенно.
Отсюда рецепт:
- общая база:
@SpringBootTest,@ActiveProfiles("test"), общие моки и Testcontainers объявлены в одном абстрактном классе или в своей мета-аннотации вроде@IntegrationTest; - одинаковые моки: свой набор
@MockitoBeanв каждом классе даёт новый ключ и новый контекст. Лучше вынести все подмены в базу, даже если классу нужна одна; - без лишнего
@DirtiesContext: он выбрасывает контекст из кеша, и следующий класс поднимет его заново; - свойства: разные
@TestPropertySourceиpropertiesв@SpringBootTestтоже меняют ключ; - срезы:
@WebMvcTestи@DataJpaTestподнимают свои, более лёгкие контексты. Это нормально, но их конфигурацию тоже держите одинаковой.
Кеш живёт в одной JVM, поэтому Surefire или Gradle должны гонять классы в переиспользуемых процессах. Размер кеша по умолчанию 32 контекста, меняется свойством spring.test.context.cache.maxSize, а попадания и промахи видно в логе на уровне DEBUG для org.springframework.test.context.cache. @MockBean в Spring Boot 3.4 объявлен устаревшим, замена @MockitoBean из Spring Framework 6.2.
Где спотыкаются
Думают, что @TestInstance(Lifecycle.PER_CLASS) экономит подъём контекста. Он влияет только на число экземпляров тестового класса, контекст и так общий для всех методов.
Рассыпают разные моки по классам и удивляются, что тесты идут двадцать минут.
Ставят @DirtiesContext «на всякий случай», когда тест меняет данные. Обычно достаточно откатить транзакцию или почистить таблицы.