Тестирование приложения на Spring Boot
Spring Boot предусматривает ряд утилит и аннотаций, упрощающих тестирование приложения. Поддержка тестирования обеспечивается двумя модулями: spring-boot-test содержит основные элементы, а spring-boot-test-autoconfigure поддерживает автоконфигурацию для тестов.
Большинство разработчиков используют «стартер» spring-boot-starter-test , который импортирует оба тестовых модуля Spring Boot, а также JUnit Jupiter, AssertJ, Hamcrest и ряд других полезных библиотек.
Если у вас есть тесты, использующие JUnit 4, для их запуска можно использовать винтажный движок JUnit 5. Чтобы использовать винтажный движок, добавьте зависимость от junit-vintage-engine , как показано в следующем примере:
hamcrest-core исключен в пользу org.hamcrest:hamcrest , который является частью spring-boot-starter-test .
Зависимости тестовой области доступности
«стартер» spring-boot-starter-test (в test scope ) содержит следующие предусмотренные библиотеки:
JUnit 5: Стандарт де-факто для модульного тестирования Java-приложений.
Spring Test и Spring Boot Test: Средства поддержки утилит и интеграционных тестов для приложений Spring Boot.
AssertJ: Библиотека текучих утверждений.
Hamcrest: Библиотека объектов-сопоставителей (matchers) (также известных как ограничения или предикаты).
Mockito: Java-фреймворк для имитаций (мокирования)
JSONassert: Библиотека утверждений для JSON.
Обычно мы находим эти общие библиотеки полезными при написании тестов. Если эти библиотеки не удовлетворяют вашим потребностям, то можно добавить дополнительные тестовые зависимости по своему усмотрению.
Тестирование приложений Spring
Одно из главных преимуществ внедрения зависимостей заключается в том, что оно должно облегчить модульное тестирование кода. Можно создавать экземпляры объектов с помощью оператора new , даже не вовлекая Spring. Также можно использовать объекты-имитациивместо реальных зависимостей.
Часто требуется выйти за рамки модульного тестирования и начать интеграционное тестирование (с ApplicationContext из Spring). Полезно иметь возможность проводить интеграционное тестирование без обязательного развертывания приложения или подключения к другой инфраструктуре.
Spring Framework содержит специальный тестовый модуль для такого интеграционного тестирования. Можно объявить зависимость непосредственно от org.springframework:spring-test или использовать «стартер» spring-boot-starter-test для транзитивного подключения.
Тестирование приложений Spring Boot
Приложение Spring Boot – это ApplicationContext для Spring, поэтому для его тестирования не требуется ничего особенного, кроме тех операций, которые выполняются для ванильного контекста Spring.
Spring Boot предусматривает аннотацию @SpringBootTest , которую можно использовать в качестве альтернативы стандартной аннотации @ContextConfiguration для spring-test , когда возникает потребность в функциях Spring Boot. Аннотация создает используемый в тестах ApplicationContext через SpringApplication . В дополнение к аннотации @SpringBootTest также предусмотрен ряд других аннотаций для тестирования более конкретных срезов приложения.
По умолчанию аннотация @SpringBootTest не запускает сервер. Вы можете использовать атрибут webEnvironment для аннотации @SpringBootTest для дальнейшего уточнения хода выполнения ваших тестов:
MOCK (по умолчанию) : загружает веб-контекст ApplicationContext и предоставляет объект-имитацию веб-окружения. Встроенные серверы не запускаются при использовании этой аннотации. Если веб-окружение недоступно в вашем classpath, этот режим прозрачно возвращается к созданию обычного не-веб ApplicationContext . Его можно использовать в сочетании с аннотацией @AutoConfigureMockMvc или @AutoConfigureWebTestClient для тестирования веб-приложения на основе имитации.
RANDOM_PORT : загружает WebServerApplicationContext и обеспечивает реальное веб-окружение. Встроенные серверы запускаются и прослушивают произвольный порт.
DEFINED_PORT : загружает WebServerApplicationContext и обеспечивает реальное веб-окружение. Встроенные серверы запускаются и прослушивают заданный порт (из файла application.properties ) или порт по умолчанию 8080 .
NONE : загружает ApplicationContext с помощью SpringApplication , но не предоставляет никакого веб-окружения (имитационного или иного).
Определение типа веб-приложения
Если Spring MVC доступен, конфигурируется обычный контекст приложения на основе MVC. Если имеется только Spring WebFlux, это будет определено, а контекст приложения будет сконфигурирован на основе WebFlux.
Если присутствуют оба фреймворка, приоритет отдается Spring MVC. Если необходимо протестировать реактивное веб-приложение в этом сценарии, требуется установить свойство spring.main.web-application-type :
Определение тестовой конфигурации
Если вы знакомы с Spring Test Framework, то, вероятно, привыкли использовать @ContextConfiguration(classes=…) , чтобы задавать, какую @Configuration для Spring следует загрузить. Как вариант, вы могли часто использовать вложенные классы с аннотацией @Configuration в своем тесте.
При тестировании приложений Spring Boot это обычно не требуется. Аннотации @*Test в Spring Boot осуществляют поиск вашей первичной конфигурации автоматически, если она не была определена вами явно.
Алгоритм поиска начинает работу с пакета, содержащего тест, пока не найдет класс, аннотированный @SpringBootApplication или @SpringBootConfiguration . При условии, что вы структурировали свой код адекватным образом, основную конфигурацию обычно удается найти.
Если используется тестовая аннотация для тестирования более конкретного слоя приложения, то следует избегать добавления параметров конфигурации, специфичных для конкретной области в классе приложения основного метода.
Базовая конфигурация сканирования компонентов, включающая аннотацию @SpringBootApplication , определяет фильтры исключения, которые используются для того, чтобы убедиться, что получение срезов работает так, как предполагается. Если используется явная директива с аннотацией @ComponentScan для помеченного аннотацией @SpringBootApplication класса, имейте в виду, что данные фильтры будут отключены. Если вы прибегаете к получению срезов, то следует определить их еще раз.
Если нужно настроить первичную конфигурацию, то можно использовать вложенный класс, помеченный аннотацией @TestConfiguration . В отличие от вложенного класса, помеченного аннотацией @Configuration , который используется вместо основной конфигурации приложения, вложенный класс с аннотацией @TestConfiguration используется в дополнение к основной конфигурации приложения.
Исключение тестовой конфигурации
Если приложение использует сканирование компонентов (например, при использовании аннотаций @SpringBootApplication или @ComponentScan ), можно обнаружить, что высокоуровневые конфигурационные классы, которые были созданы только для определенных тестов, случайно перехватываются повсюду.
Аннотацию @TestConfiguration можно использовать во внутреннем тестовом классе для настройки первичной конфигурации. Если аннотацией @TestConfiguration помечает высокоуровневый класс, то она указывает, что классы в src/test/java не должны перехватываться при сканировании. Затем можно импортировать этот класс в явном виде, когда это необходимо, как показано в следующем примере:
Обзор модульного и интеграционного тестирования Spring Boot
Модульное и интеграционное тестирование — неотъемлемая часть вашей повседневной жизни как разработчика. Однако для новичков Spring Boot написание содержательных тестов для своих приложений оказывается проблемой:
С чего начать мои усилия по тестированию?
Как Spring Boot может помочь мне в написании эффективных тестов?
Какие библиотеки мне использовать?
В этом блоге вы получите обзор того, как модульное и интеграционное тестирование работает со Spring Boot. Кроме того, вы узнаете, на каких функциях и библиотеках Spring следует сосредоточиться в первую очередь. Эта статья действует как агрегатор, и в нескольких местах вы найдете ссылки на другие статьи и руководства, которые более подробно объясняют концепции.
Модульное тестирование с помощью Spring Boot
Модульные тесты составляют основу вашей стратегии тестирования. Каждый проект Spring Boot, который вы запускаете с помощью Spring Initializr, имеет прочную основу для написания модульных тестов. Настраивать практически нечего, так как Spring Boot Starter Test включает в себя все необходимые строительные блоки.
Помимо включения и управления версией Spring Test, этот Spring Boot Starter включает и управляет версиями следующих библиотек:
Библиотеки проверки утверждений, такие как AssertJ, Hamcrest, JsonPath и т. Д.
Вы можете найти введение в этот швейцарский нож тестирования и включенных в него библиотеках тестирования в этом сообщении блога.
В большинстве случаев ваши модульные тесты не нуждаются в какой-либо конкретной функции Spring Boot или Spring Test, поскольку они будут полагаться исключительно на JUnit и Mockito.
С помощью модульных тестов вы изолированно тестируете, например, свои *Service классы и имитируете каждого сотрудника тестируемого класса:
Как видно из раздела import тестового класса выше, Spring вообще не включается. Следовательно, вы можете применять методы и знания, полученные из модульного тестирования любого другого приложения Java.
Вот почему важно изучить основы JUnit 4/5 и Mockito, чтобы максимально использовать возможности модульного тестирования.
Для некоторых частей вашего приложения модульное тестирование не принесет особой пользы. Хорошими примерами для этого являются уровень персистентности или тестирование HTTP-клиента. Тестируя такие части вашего приложения, вы в конечном итоге почти копируете свою реализацию, поскольку вам приходится имитировать много взаимодействий с другими классами.
Лучшим подходом здесь является работа с нарезанным контекстом Spring, который можно легко автоматически настроить с помощью аннотаций теста Spring Boot.
Тесты фрагментов Spring Context
В дополнение к традиционным модульным тестам вы можете писать тесты с помощью Spring Boot, которые нацелены на определенные части (фрагменты) вашего приложения. TestContext Spring фреймворка вместе с Spring Boot адаптирует Spring контекст с достаточным количеством компонентов для конкретного теста.
Цель этих тестов — изолированно протестировать определенную часть вашего приложения без запуска всего приложения. Это сокращает как время выполнения теста, так и потребность в обширной настройке теста.
Как назвать такие тесты? На мой взгляд, они не попадают на 100% в категорию модульных или интеграционных тестов. Некоторые разработчики называют их модульными тестами, потому что они тестируют, например, один контроллер изолированно. Другие разработчики относят их к интеграционным тестам, поскольку в них задействована поддержка Spring. Как бы вы их ни называли, убедитесь, что у вас есть общее понимание, по крайней мере, в вашей команде.
Spring Boot предлагает много аннотаций, позволяющих проверить различные части вашего приложения в отдельности: @JsonTest , @WebMvcTest , @DataMongoTest , @JdbcTest и т.д.
Все они автоматически настраивают фрагменты Spring TestContext и включают только компоненты Spring, относящиеся к тестированию определенной части вашего приложения. Я посвятил целую статью представлению наиболее распространенных из этих аннотаций и объяснению их использования.
Две наиболее важные аннотации (сначала изучите их):
Также доступны аннотации для других нишевых частей вашего приложения:
При их использовании важно понимать, какие компоненты входят в состав, TestContext а какие нет. Документация Javadoc каждой аннотации объясняет выполненную автоконфигурацию и цель.
Вы всегда можете расширить контекст автонастройки для своего теста, явно импортировав компоненты с помощью @Import или определив дополнительные компоненты Spring Beans, используя @TestConfiguration :
Вы можете найти дополнительные методы устранения потенциальных исключений NoSuchBeanDefinitionException, с которыми вы можете столкнуться при таких тестах, в этом сообщении в блоге.
Ловушка JUnit 4 и JUnit 5
Одна большая ловушка, с которой я довольно часто сталкиваюсь, отвечая на вопросы на Stack Overflow, — это сочетание JUnit 4 и JUnit 5 (JUnit Jupiter, если быть более конкретным) в одном тесте. Использование API разных версий JUnit в одном тестовом классе может привести к неожиданным результатам и сбоям.
Важно следить за импортом, особенно за @Test аннотацией:
Другими индикаторами для JUnit 4 являются: @RunWith , @Rule , @ClassRule , @Before , @BeforeClass , @After , @AfterClass .
С помощью JUnit 5 vintage-engine ваш набор тестов может содержать как тесты JUnit 3/4, так и JUnit Jupiter, но каждый тестовый класс может использовать только одну конкретную версию JUnit. Рассмотрите возможность миграции существующих тестов, чтобы использовать различные новые функции JUnit Jupiter (параметризованные тесты, распараллеливание, модель расширения и т. д.). Вы можете постепенно мигрировать свой набор тестов, так как вы можете запускать тесты JUnit 3/4 рядом с тестами JUnit 5.
Документация JUnit включает советы по миграции JUnit 4, а также имеются инструменты (JUnit Pioneer или эта функция IntelliJ) для автоматической миграции тестов (например, импорт или проверки утверждений).
После того, как вы мигрировали свой набор тестов на JUnit 5, важно исключить любое появление устаревшей версии JUnit. Не все в вашей команде могут постоянно обращать пристальное внимание на импорт библиотек тестирования. Чтобы избежать случайного смешивания разных версий JUnit, исключение их из вашего проекта помогает всегда выбирать правильный импорт:
Кроме Spring Boot Starter Test другие зависимости тестования также могут включать более старые версии JUnit:
Чтобы избежать (случайного) включения зависимостей JUnit 4 в будущем, вы можете использовать Maven Enforcer Plugin и определить его как запрещенную зависимость. Это приведет к сбою сборки, как только кто-то включит новую тестовую зависимость, которая транзитивно потянет JUnit 4.
Обратите внимание, что, начиная с Spring Boot 2.4.0, зависимость Spring Boot Starter Test больше не включает vintage-engine файл по умолчанию.
Интеграционные тесты с Spring Boot: @SpringBootTest
С помощью интеграционных тестов вы обычно тестируете несколько компонентов вашего приложения в комбинации. Большая часть времени вы будете использовать @SpringBootTest аннотацию для этой цели и доступ к приложению с внешней стороны с помощью либо WebTestClient или TestRestTemplate .
@SpringBootTest будет заполнять весь контекст приложения для теста. При использовании этой аннотации важно понимать атрибут webEnvironment . Без указания этого атрибута такие тесты не будут запускать встроенный контейнер сервлетов (например, Tomcat) и вместо этого будут использовать имитацию среды сервлетов. Следовательно, ваше приложение не будет доступно через локальный порт.
Вы можете переопределить это поведение, указав либо, DEFINE_PORT либо RANDOM_PORT :
Для интеграционных тестов, которые запускают встроенный контейнер сервлетов, вы можете затем внедрить порт своего приложения и получить к нему доступ извне, используя TestRestTemplate или WebTestClient :
Поскольку TestContext Spring фреймворка будет заполнять весь контекст приложения, вы должны убедиться, что присутствуют все зависимые компоненты инфраструктуры (например, база данных, очереди сообщений и т. д.).
Здесь в игру вступают Testcontainers. Testcontainers будет управлять жизненным циклом любого Docker контейнера для вашего теста:
Для ознакомления с Testcontainers рассмотрите следующие ресурсы:
Если ваше приложение обменивается данными с другими системами, вам нужно решение для имитации этого HTTP-взаимодействия. Это довольно часто бывает, когда вы, например, получаете данные из удаленного REST API или токенов доступа OAuth2 при запуске приложения. С помощью WireMock вы можете заглушить и подготовить HTTP-ответы для имитации удаленной системы.
Кроме того, TestContext Spring фреймворк имеет удобную функцию кеширования и повторного использования, а также уже запущенный контекст. Это может помочь сократить время сборки и значительно улучшить циклы обратной связи.
Сквозные тесты с Spring Boot
Целью сквозных (E2E) тестов является проверка системы с точки зрения пользователя. Сюда входят тесты для основных сценариев работы пользователя (например, размещение заказа или создание нового клиента). По сравнению с интеграционными тестами такие тесты обычно включают пользовательский интерфейс (если он есть).
Вы также можете выполнить тесты E2E для развернутой версии приложения, например, в среде dev или staging прежде чем приступить к развертыванию в рабочей среде.
Для приложений, которые используют рендеринг на стороне сервера (например, Thymeleaf) или автономной системы, когда серверная часть Spring Boot обслуживает интерфейс, вы можете использовать @SpringBootTest для этих тестов.
Как только вам нужно взаимодействовать с браузером, Selenium обычно является выбором по умолчанию. Если вы какое-то время работали с Selenium, вы сможете обнаружить, что снова и снова реализуете одни и те же вспомогательные функции. Для лучшего взаимодействия с разработчиками и уменьшения головной боли при написании тестов, предполагающих взаимодействие с браузером, рассмотрите вариант Selenide. Selenide — это абстракция поверх низкоуровневого API Selenium для написания стабильных и кратких тестов браузера.
Следующий тест демонстрирует, как получить доступ и протестировать общедоступную страницу приложения Spring Boot с помощью Selenide:
Вы можете найти больше информации о Selenide в этом сообщении в блоге.
Для компонентов инфраструктуры, которые необходимо запустить для тестов E2E, Testcontainers играет большую роль. Если вам нужно запустить несколько Docker контейнеров, вам пригодится модуль Docker Compose из Testcontainers :
Резюме
Spring Boot предлагает отличную поддержку как для модульного, так и для интеграционного тестирования. Это делает тестирование первоклассным гражданином, поскольку каждый проект Spring Boot включает в себя Spring Boot Starter Test. Этот стартер подготовит ваш базовый набор инструментов для тестирования с необходимыми библиотеками тестирования.
Кроме того, аннотации Spring Boot test упрощают написание тестов для различных частей вашего приложения. Вы получите специально созданный Spring TestContext только с соответствующими Spring beans.
Чтобы познакомиться с модульными и интеграционными тестами для ваших проектов Spring Boot, рекомендуются следующие шаги:
Избегайте ловушки JUnit 4 и JUnit 5.
Ознакомьтесь с различными аннотациями тестирования Spring Boot, которые автоматически настраивают фрагменты контекста.
Узнайте, как Spring TestContext Caching может помочь сократить общее время выполнения вашего набора тестов.
Если ваш тест по-прежнему не выполняет то, что вы ожидаете, не сокращайте свои усилия по тестированию, оправдывая это тем, что Spring Boot — это слишком много магии. Как в документации Spring, так и в различных блогах доступен отличный материал.
Кроме того, активность сообщества на Stack Overflow для таких тегов, как spring-test , spring-boot-test или spring-test-mvc , довольно хороша, и есть большая вероятность, что вы получите помощь. Я также часто отвечаю на вопросы, связанные с тестированием, на Stack Overflow.
Удачного модульного и интеграционного тестирования с помощью Spring Boot,
Как писать unit тесты для java spring и Jest?
![]()
Я преподаю уже месяц и заметил такую вещь, ученики не понимают, как писать unit тесты. Ни в учебных задачах, ни в рабочих проектах. Прежде чем разобраться в этом, давайте посмотрим, какие проблемы позволяют решить unit тесты.
Что здесь может пойти не так? С точки зрения интерпретатора, здесь нет ошибок, код запускается и работает. Даже исключения не выбрасывает. Покрайне мере, мы об этом не знаем. Пока.
Опа! Всё-таки можно сломать этот код и получить исключение. То, что мы сейчас сделали, называется деструктивный тест. «Проверка кода на прочность», найти слабые места.
Хорошо, что это даёт? А то, что теперь мы можем написать обработчик такой ситуации и выдавать более осмысленное исключение, которое уже будет содержать информацию о том, как его избежать. Обратите внимание, код даже не побывал в «боевой» production среде, а мы уже исправили ошибку! Разве не круто?
Функция, которую я привёл в пример является «чистой». То-есть, результат ее работы зависит только от входных аргументов. Соответсвенно, можно что-либо испортить только путём подачи на вход неправильных аргументов.
В случае с чистыми функциями, тесты написать легче всего. Как правило, проекты на java spring почти всегда состоят из классов и методов. А отличие метода от класса в том, что он по-умолчанию имеет доступ к внутреннему состоянию объекта класса. То-есть, его работа зависит от какого-то скрытого для внешнего наблюдателя состояния, которое он не может контролировать. Такие методы/функции называются грязными, в терминах функционального программирования.
Что же делать? Как в таком случае писать тесты для этих грязных функций? Прежде чем ответить на этот вопрос я приведу несколько примеров грязного кода. Это может быть не только отдельно взятая подпрограмма, но даже класс и проект в целом, если он использует какие-то внешние библиотеки, базы данных, службы итд. Почему так? Потому что у вас нет полного контроля за ситуацией. Те же базы данных работают по принципу чёрного ящика. Мы можем писать туда данные. Позже их можно считать и производить другие манипуляции. Но контроля за выполнением этого функционала у вас нет. В случае с подключаемыми библиотеками, доподлинно не известно все то, что может происходить внутри, когда вы используете библиотеку. Конечно, если вы не излучили от и до исходные коды используемых библиотек. Вот пример, как сделать из функции выше “грязную”:
Теперь, при написании теста для этой функции нельзя гарантировать, что он будет срабатывать всегда, тк новая функция div зависит от переменной divider. В данном примере, я могу сломать тест, если изменю значение divider, при том что сама функция работает корректно.
Я привел простой пример, но в “боевых” условиях, у вас будет больше таких зависимостей. Как можно исправить функцию из предыдущего примера?
Нужно сделать функцию чистой. Для этого, я перенес переменную divider в аргументы функции. Теперь, вызывающая сторона полностью контроллирует поведение функции, как и ее результат. Значит, мы можем написать unit тест, который будет работать в любых условиях.
Бывают более сложные ситуации, когда таких зависимостей может быть не одна, как в случае с глобальной переменной выше, а восемь, например. В таком случае, будет неправильно и не удобно переносить их все в аргументы. Хотя технически, это возможно. Лучшим решением, на мой взгляд, применить паттерн Dependency Injection. А также, можно “упаковать” часто используемые аргументы в некий общий контекст. Например, сделать класс. В предыдущей статье https://cutt.ly/bc5brXn есть примеры, как это сделать.
Я много говорю о грязных функциях, потому что они препятствуют написанию качественных unit тестов. Что же вообще происходит в этих тестах? Их задача – своевременно обнаружить логические ошибки, а также другие ошибки, которые не может найти компилятор или интерпретатор. Например, код на JavaScript иногда требует писать проверки типов в ручную, тогда как в Java вы не сможете использовать Boolean на месте String. Это пример простой ошибки. Но бывают ситуации, когда нет иного выхода, кроме как написать код, который зависит от тех или иных условий. Например, от порядка вызова процедур.
Приведу пример. Есть некая игра, в которой оставшиеся деньги пользователя при выходе или проигрыше распределяются поровну между остальными игроками. Чтобы это реализовать, нужно получить баланс проигравшего пользователя, а потом разделить его на количество оставшихся игроков и добавить получишуюся сумму каждому из них.
Для этой задачи критически важно, чтобы пятая строка шла после удаления пользователя на четвертой строке. Хотя можно было бы и поместить ее в начало функции и она бы работала. Но неправильно.
Unit тест в данном примере может защитить от человеческого фактора, осуществляя автоматическую проверку, что все идёт своим чередом. Чтобы у будущие программисты знали нюансы работы с вашей системой без чтения документации. У вас есть документация системы, кстати? То-то же.
Используем базу данных для временных данных unit тестов
Никогда так не делайте. Максимум, использовать in memory базы, по типу SQLite или H2. Ваш тест не должен чём-либо ограничиваться. Если вы пишите в любую базу данных в тесте, значит, для запуска этих тестов нужна база данных. Все члены команды будут обязаны ее себе поставить. Подумайте, вы бы хотели устанавливать на собственную машину софт, который нужен для запуска unit тестов?
Тоже самое можно сказать о зависимостях на каком-либо сайте, веб службе, будь то бакеты на амазоне, различные cdn, распределенные хранилища, очереди. Чем больше проект, тем больше у него будет таких зависимостей. Тем тяжелее будет запустить unit тесты. Поэтому, ваша задача абстрагироваться в unit тестах от внешних сервисов, с помощью моков.
В названии статьи я упомянул тесты для java spring, давайте теперь посмотрим, как сделать мок базы данных в помощью Mockito.
Мок объект – это хороший пример практического применения паттерна проектирования Proxy. Его задача состоит в отслеживании подотчётных ему вызовов методов. Кроме этого, вы можете сделать stubbing, чтобы заменить вызовы «грязного» кода на что-то более приемлемое. Например, заменить обращения к базе данных на ваши собственные методы, объявленные прямо внутри unit тестов. За счет этого тесты станут независимыми от среды исполнения и их можно будет задействовать в CI/CD пайплайнах. Например:
Имеется FileService, в нем нужно протестировать метод setDeletedStatusForFileByUuid.
Я знаю, что внутри тестируемого метода должен вызываться fileRepository.save, поэтому, в тесте сделаю эту проверку.
Здесь вызов тестируемого метода просходит на девятой строке. Я знаю, что внутри него используется fileRepository, который отвечает за связь с базой данных. Чтобы подменить этот вызов, я использую статический метод when класса Mockito. В седьмой строке я указываю, какое значение должно вернуться из метода findById. Обратите внимание, что подготовить мок нужно до вызова тестируемого метода. И в заключении, я проверяю, что был вызван метод fileRepository.save. Если этого не произойдет, тест вернет ошибку.
Таким образом, мы реализовали проверку бизнес логики. В случае, если кто-то удалит вызовы методов, которые мы проверили, тесты помогут сразу выявить эту ошибку. Хотя с точки зрения компилятора — все ок. Тестировать таким образом нужно те методы, которые критичны для вас.
Как понять, что критично, а что нет?
Например, в примере выше я помечаю файл как удаленный, перед этим проверяя, что он существует в базе данных. Значит, я должен проверить, что точно происходит чтение из базы данных. Если этого, по какой-то причине, не произошло, значит, я могу с уверенностью заявить, что это ошибочное поведение. Тест должен выявлять подобные ситуации. Также, в примере выше я поставил проверку на вызов записи в базу данных обновленного файла. Опять же, если этого не произошло — это ошибка.
Я показал самые простые моки и проверки. Кроме этого, вы можете проверять возвращаемые значения с помощью assert-ов, конкретные аргументы у методов моков. Допустим, можно проверить, что метод сохранения был вызвал с одним аргументом типа String. И затем сравнить эту строку с эталоном, который возвращает рабочий код.
Самое сложное — уметь выявлять зависимости вашего кода
Testing the Web Layer
This guide walks you through the process of creating a Spring application and then testing it with JUnit.
What You Will Build
You will build a simple Spring application and test it with JUnit. You probably already know how to write and run unit tests of the individual classes in your application, so, for this guide, we will concentrate on using Spring Test and Spring Boot features to test the interactions between Spring and your code. You will start with a simple test that the application context loads successfully and continue on to test only the web layer by using Spring’s MockMvc .
What You Need
About 15 minutes
A favorite text editor or IDE
You can also import the code straight into your IDE:
How to complete this guide
Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.
To start from scratch, move on to Starting with Spring Initializr.
To skip the basics, do the following:
Download and unzip the source repository for this guide, or clone it using Git: git clone https://github.com/spring-guides/gs-testing-web.git
cd into gs-testing-web/initial
When you finish, you can check your results against the code in gs-testing-web/complete .
Starting with Spring Initializr
You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.
To manually initialize the project:
Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.
Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.
Click Dependencies and select Spring Web.
Click Generate.
Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.
| If your IDE has the Spring Initializr integration, you can complete this process from your IDE. |
| You can also fork the project from Github and open it in your IDE or other editor. |
Create a Simple Application
Create a new controller for your Spring application. The following listing (from src/main/java/com/example/testingweb/HomeController.java ) shows how to do so:
| The preceding example does not specify GET versus PUT , POST , and so forth. By default @RequestMapping maps all HTTP operations. You can use @GetMapping or @RequestMapping(method=GET) to narrow this mapping. |
Run the Application
The Spring Initializr creates an application class (a class with a main() method) for you. For this guide, you need not modify this class. The following listing (from src/main/java/com/example/testingweb/TestingWebApplication.java ) shows the application class that the Spring Initializr created:
@SpringBootApplication is a convenience annotation that adds all of the following:
@Configuration : Tags the class as a source of bean definitions for the application context.
@EnableAutoConfiguration : Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.
@EnableWebMvc : Flags the application as a web application and activates key behaviors, such as setting up a DispatcherServlet . Spring Boot adds it automatically when it sees spring-webmvc on the classpath.
@ComponentScan : Tells Spring to look for other components, configurations, and services in the the com.example.testingweb package, letting it find the HelloController class.
The main() method uses Spring Boot’s SpringApplication.run() method to launch an application. Did you notice that there is not a single line of XML? There is no web.xml file, either. This web application is 100% pure Java and you did not have to deal with configuring any plumbing or infrastructure. Spring Boot handles all of that for you.
Logging output is displayed. The service should be up and running within a few seconds.
Test the Application
Now that the application is running, you can test it. You can load the home page at http://localhost:8080 . However, to give yourself more confidence that the application works when you make changes, you want to automate the testing.
| Spring Boot assumes you plan to test your application, so it adds the necessary dependencies to your build file ( build.gradle or pom.xml ). |
The first thing you can do is write a simple sanity check test that will fail if the application context cannot start. The following listing (from src/test/java/com/example/testingweb/TestingWebApplicationTest.java ) shows how to do so:
The @SpringBootTest annotation tells Spring Boot to look for a main configuration class (one with @SpringBootApplication , for instance) and use that to start a Spring application context. You can run this test in your IDE or on the command line (by running ./mvnw test or ./gradlew test ), and it should pass. To convince yourself that the context is creating your controller, you could add an assertion, as the following example (from src/test/java/com/example/testingweb/SmokeTest.java ) shows:
Spring interprets the @Autowired annotation, and the controller is injected before the test methods are run. We use AssertJ (which provides assertThat() and other methods) to express the test assertions.
| A nice feature of the Spring Test support is that the application context is cached between tests. That way, if you have multiple methods in a test case or multiple test cases with the same configuration, they incur the cost of starting the application only once. You can control the cache by using the @DirtiesContext annotation. |
It is nice to have a sanity check, but you should also write some tests that assert the behavior of your application. To do that, you could start the application and listen for a connection (as it would do in production) and then send an HTTP request and assert the response. The following listing (from src/test/java/com/example/testingweb/HttpRequestTest.java ) shows how to do so:
Note the use of webEnvironment=RANDOM_PORT to start the server with a random port (useful to avoid conflicts in test environments) and the injection of the port with @LocalServerPort . Also, note that Spring Boot has automatically provided a TestRestTemplate for you. All you have to do is add @Autowired to it.
Another useful approach is to not start the server at all but to test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. That way, almost of the full stack is used, and your code will be called in exactly the same way as if it were processing a real HTTP request but without the cost of starting the server. To do that, use Spring’s MockMvc and ask for that to be injected for you by using the @AutoConfigureMockMvc annotation on the test case. The following listing (from src/test/java/com/example/testingweb/TestingWebApplicationTest.java ) shows how to do so:
In this test, the full Spring application context is started but without the server. We can narrow the tests to only the web layer by using @WebMvcTest , as the following listing (from src/test/java/com/example/testingweb/WebLayerTest.java ) shows:
The test assertion is the same as in the previous case. However, in this test, Spring Boot instantiates only the web layer rather than the whole context. In an application with multiple controllers, you can even ask for only one to be instantiated by using, for example, @WebMvcTest(HomeController.class) .
So far, our HomeController is simple and has no dependencies. We could make it more realistic by introducing an extra component to store the greeting (perhaps in a new controller). The following example (from src/main/java/com/example/testingweb/GreetingController.java ) shows how to do so:
Then create a greeting service, as the following listing (from src/main/java/com/example/testingweb/GreetingService.java ) shows:
Spring automatically injects the service dependency into the controller (because of the constructor signature). The following listing (from src/test/java/com/example/testingweb/WebMockTest.java ) shows how to test this controller with @WebMvcTest :
We use @MockBean to create and inject a mock for the GreetingService (if you do not do so, the application context cannot start), and we set its expectations using Mockito .
Summary
Congratulations! You have developed a Spring application and tested it with JUnit and Spring MockMvc and have used Spring Boot to isolate the web layer and load a special application context.
See Also
The following guides may also be helpful:
Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.