При написании интеграционных тестов для 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Теперь, если во время выполнения тестов будет превышено количество инициализированных тестовых контекстов, то мы увидим ошибку в тестах и сборка завершится неудачей.

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