Как стать автором
Обновить

Комментарии 2

Поддерживаю написанное выше, на нашем проекте со временем пришли к тем же выводам. Дополню несколько интересных моментов.

Использовать Spring-контекст гарантированно один раз за прогон всех тестов помогает вот такая простая штука:

@Slf4j
public class FailFastContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final AtomicBoolean flag = new AtomicBoolean();

    @SuppressFBWarnings("DM_EXIT")
    @Override
    public void initialize(@NonNull ConfigurableApplicationContext applicationContext) {
        boolean error = flag.getAndSet(true);
        if (error) {
            // throw new IllegalStateException("Second application context start attempt.");
            log.error("""
                              Second application context start attempt.
                              ####################################################
                              #     SECOND APPLICATION CONTEXT START ATTEMPT.    #
                              #     -----------------------------------------    #
                              #  Please look upper for the reason why Spring is  #
                              #  trying to recreate test context.  This can be   #
                              #  caused by adding an @Import/@MockBean and some  #
                              #  other annotations to the test class.            #
                              #                                                  #
                              #  This process will now exit immediately.         #
                              ####################################################
                              """);
            System.exit(-1);
        }
    }
}

Нужно только подложить его в @ContextConfiguration(initializers={...}) на главном тестовом классе приложения BaseApplicationTest (от которого удобно наследовать все непосредственно тестовые классы).

Вызовы своего боевого API совершаем через честный http-клиент. Это даёт небольшие накладные расходы на сериализацию/десериализацию, зато дополнительно покрывает: конфигурацию Jackson (кто не ловил, что Instant сериализуется в массив чисел?), цепочку фильтров (особенно если подложены свои фильтры), настройки приложения (вкл/выкл OSiV?) и т.п. В общем, успешно пройденный тест даёт ещё больше гарантий, чем вызов напрямую контроллеров.

Иногда (в полной мере этого слова) — всё-таки приходится искать способы внедрить какие-то ассерты в кишки боевого кода. Для этого можно придумать отдельный интерфейс, похожий на обычные функциональные (Function, Consumer, Supplier, в зависимости от вашей ситуации), и инжектить в боевой код Optional<ThisInterface> и дёргать в нужном месте. Естественно, в боевом контексте не будет реализации и ничего выполняться не будет. В тестах подкладывать собственную реализацию этого интерфейса, инжектить в свой тестовый класс, использовать. Обычно внутри реализации прячется какой-нибудь Exchange, AtomicReference или какой-то такой примитив для обмена данными.

Не стесняйтесь в тестовом коде писать свои контроллеры, сервисы и даже репозитории. Все они могут помочь не строить костыли. Или можно написать просто класс-accessor для package-private данных в каком-то боевом пакете, просто расположив его в нём же и сделав публичным. Сделать какой-то боевой код (классы, методы) package-private вместо private — может быть приемлемым компромиссом в ряде случаев. Я тогда в комментариях так и пишу "Является package-private для целей тестирования".

Ну и самая пушка в нашем проекте — гарантия от тестовой инфраструктуры в сторону теста, что БД всегда чиста. Требует некоторое время для реализации, даёт некоторые накладные расходы, зато как приятно ощущать, что тест действительно изолирован от остальных. Очень радует и при написании новых, и при исправлении имеющихся.

В заключении хочется повторить Uncle Боба — относитесь к коду своих тестов также, как к боевому, и ни в коем случае не как к коду второго сорта.

Спасибо за совет. Громкий ЛОГ при выходе действительно хорошо подсвечивает то, что несколько запусков по прохождении тестов.

Под тестами контроллеров имеются в виду тесты @MockMvc - они полностью имитируют запрос и ответ к нему. Обращение к методу контроллера происходит с помощью URL.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий