При написании интеграционных тестов для Spring Boot приложения часто возникает проблема, что разработчики бездумно добавляют аннотации @MockBean, @SpyBean, @DirtiesContext или переопределяют прямо в тестовом классе различные property. Всё это приводит к изменению Spring Context, невозможности использовать закэшированный контекст и следовательно созданию нового. Часто создание нового контекста это длительная операция.
Существуют инструменты по отслеживанию этих процессов. Самым простым способом увидеть количество контекстов и количество попаданий в кэш является добавление логирования либо через свойство logging.level.org.springframework.test.context.cache=DEBUG либо настройкой вашего логгера.
Один известный автор статей про тестирование на Java / Spring Boot, Philip Riecks (со товарищи), создал инструмент с открытым исходным кодом Spring Test Profiler при помощи которого можно получить html отчёт о поднимаемых контекстах во время тестов, о количестве и типе бинов в этих контекстах. На Хабре есть перевод его статьи в сообществе Spring АйО.
У нас на проекте стал вопрос, как нам показать разработчикам, что их тест порождает новый Спринг Контекст. Мы решили считать контексты в тестах и при превышении ожидаемого количества падать. Это "руинит" сборку и CI/CD пайплайн.
Для этого мы добавили реализацию интерфейса ContextCustomizer
class LimitingSpringContextCustomizer implements ContextCustomizer { private static final AtomicInteger CONTEXT_COUNTER = new AtomicInteger(); private static final int EXPECTED_SPRING_TEST_CONTEXT_COUNT = 16; @Override public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { int numberOfContexts = CONTEXT_COUNTER.incrementAndGet(); log.info("Number of Spring Test Contexts: {}/{}", numberOfContexts, EXPECTED_SPRING_TEST_CONTEXT_COUNT); Assert.state(numberOfContexts <= EXPECTED_SPRING_TEST_CONTEXT_COUNT, () -> "Number of test contexts exceeds configured maximum: " + EXPECTED_SPRING_TEST_CONTEXT_COUNT); } @Override public boolean equals(Object obj) { return (obj != null) && (getClass() == obj.getClass()); } @Override public int hashCode() { return getClass().hashCode(); } }
Здесь, согласно документации интерфейса ContextCustomizer необходимо корректно переопределить методы equals и hashCode.
WARNING: implementations must implement correct equals and hashCode methods since customizers form part of the MergedContextConfiguration which is used as a cache key.
Далее добавляем фабрику.
class LimitingSpringContextCustomizerFactory implements ContextCustomizerFactory { @Override public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) { return new LimitingSpringContextCustomizer(); } }
Затем регистрируем эту фабрику при помощи spring.factories чтобы она применялась ко всем тестам.
Для этого создаём в тестовых ресурсах файл по пути src/test/resources/META-INF/spring.factories со следующим содержимым
org.springframework.test.context.ContextCustomizerFactory=\ com.mycompany.app.support.spring.LimitingSpringContextCustomizerFactory
Теперь, если во время выполнения тестов будет превышено количество инициализированных тестовых контекстов, то мы увидим ошибку в тестах и сборка завершится неудачей.

Возможно, это пример поможет кому-нибудь повысить скорость прохождения своих тестов путём отслеживания количества запускаемых тестовых Спринг контекстов.
