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, как показано в следующем примере:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
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.
-
JsonPath: XPath для JSON.
Обычно мы находим эти общие библиотеки полезными при написании тестов. Если эти библиотеки не удовлетворяют вашим потребностям, то можно добавить дополнительные тестовые зависимости по своему усмотрению.
Тестирование приложений Spring
Одно из главных преимуществ внедрения зависимостей заключается в том, что оно должно облегчить модульное тестирование кода. Можно создавать экземпляры объектов с помощью оператора new, даже не вовлекая Spring. Также можно использовать объекты-имитациивместо реальных зависимостей.
Часто требуется выйти за рамки модульного тестирования и начать интеграционное тестирование (с ApplicationContext из Spring). Полезно иметь возможность проводить интеграционное тестирование без обязательного развертывания приложения или подключения к другой инфраструктуре.
Spring Framework содержит специальный тестовый модуль для такого интеграционного тестирования. Можно объявить зависимость непосредственно от org.springframework:spring-test или использовать "стартер" spring-boot-starter-test для транзитивного подключения.
Тестирование приложений Spring Boot
Приложение Spring Boot – это ApplicationContext для Spring, поэтому для его тестирования не требуется ничего особенного, кроме тех операций, которые выполняются для ванильного контекста Spring.
SpringApplication для его создания.
Spring Boot предусматривает аннотацию @SpringBootTest, которую можно использовать в качестве альтернативы стандартной аннотации @ContextConfiguration для spring-test, когда возникает потребность в функциях Spring Boot. Аннотация создает используемый в тестах ApplicationContext через SpringApplication. В дополнение к аннотации @SpringBootTest также предусмотрен ряд других аннотаций для тестирования более конкретных срезов приложения.
@RunWith(SpringRunner.class) к тесту, иначе аннотации будут проигнорированы. Если вы используете JUnit 5, нет необходимости добавлять эквивалент аннотации
@ExtendWith(SpringExtension.class), поскольку аннотация
@SpringBootTest и другие аннотации
@…Test уже аннотированы им.
По умолчанию аннотация @SpringBootTest не запускает сервер. Вы можете использовать атрибут webEnvironment для аннотации @SpringBootTest для дальнейшего уточнения хода выполнения ваших тестов:
-
MOCK(по умолчанию) : загружает веб-контекстApplicationContextи предоставляет объект-имитацию веб-окружения. Встроенные серверы не запускаются при использовании этой аннотации. Если веб-окружение недоступно в вашем classpath, этот режим прозрачно возвращается к созданию обычного не-вебApplicationContext. Его можно использовать в сочетании с аннотацией@AutoConfigureMockMvcили@AutoConfigureWebTestClientдля тестирования веб-приложения на основе имитации. -
RANDOM_PORT: загружаетWebServerApplicationContextи обеспечивает реальное веб-окружение. Встроенные серверы запускаются и прослушивают произвольный порт. -
DEFINED_PORT: загружаетWebServerApplicationContextи обеспечивает реальное веб-окружение. Встроенные серверы запускаются и прослушивают заданный порт (из файлаapplication.properties) или порт по умолчанию8080. -
NONE: загружаетApplicationContextс помощьюSpringApplication, но не предоставляет никакого веб-окружения (имитационного или иного).
@Transactional, он по умолчанию откатывает транзакцию в конце каждого тестового метода. Однако, поскольку использование этой схемы с
RANDOM_PORT или
DEFINED_PORT неявным образом предоставляет реальное окружение сервлетов, HTTP-клиент и сервер работают в отдельных потоках и, таким образом, в отдельных транзакциях. Любая транзакция, инициированная на сервере, в этом случае не откатывается.
@SpringBootTest с
webEnvironment = WebEnvironment.RANDOM_PORT также запускает сервер управления через отдельный случайный порт, если приложение использует иной порт для сервера управления.
Определение типа веб-приложения
Если Spring MVC доступен, конфигурируется обычный контекст приложения на основе MVC. Если имеется только Spring WebFlux, это будет определено, а контекст приложения будет сконфигурирован на основе WebFlux.
Если присутствуют оба фреймворка, приоритет отдается Spring MVC. Если необходимо протестировать реактивное веб-приложение в этом сценарии, требуется установить свойство spring.main.web-application-type:
@SpringBootTest(properties = "spring.main.web-application-type=reactive") class MyWebFluxTests { // ... }
@SpringBootTest(properties = ["spring.main.web-application-type=reactive"]) class MyWebFluxTests { // ... }
Определение тестовой конфигурации
Если вы знакомы с 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 не должны перехватываться при сканировании. Затем можно импортировать этот класс в явном виде, когда это необходимо, как показано в следующем примере:
@SpringBootTest @Import(MyTestsConfiguration.class) class MyTests { @Test void exampleTest() { // ... } }
@SpringBootTest @Import(MyTestsConfiguration::class) class MyTests { @Test fun exampleTest() { // ... } }
@ComponentScan используется напрямую (то есть не через аннотацию
@SpringBootApplication), необходимо зарегистрировать
TypeExcludeFilter вместе с ней.
Использование аргументов приложения
Если приложение принимает аргументы, то можно с помощью аннотации @SpringBootTest внедрить их через атрибут args.
@SpringBootTest(args = "--app.test=one") class MyApplicationArgumentTests { @Test void applicationArgumentsPopulated(@Autowired ApplicationArguments args) { assertThat(args.getOptionNames()).containsOnly("app.test"); assertThat(args.getOptionValues("app.test")).containsOnly("one"); } }
@SpringBootTest(args = ["--app.test=one"]) class MyApplicationArgumentTests { @Test fun applicationArgumentsPopulated(@Autowired args: ApplicationArguments) { assertThat(args.optionNames).containsOnly("app.test") assertThat(args.getOptionValues("app.test")).containsOnly("one") } }
Тестирование с помощью имитационного окружения
По умолчанию аннотация @SpringBootTest не запускает сервер, а вместо этого создает имитационное окружение для тестирования конечных веб-точек.
В Spring MVC можно запрашивать конечные веб-точки с помощью MockMvc или WebTestClient, как показано в следующем примере:
@SpringBootTest @AutoConfigureMockMvc class MyMockMvcTests { @Test void testWithMockMvc(@Autowired MockMvc mvc) throws Exception { mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World")); } // Если Spring WebFlux находится в classpath, то можно управлять MVC-тестами с помощью WebTestClient @Test void testWithWebTestClient(@Autowired WebTestClient webClient) { webClient .get().uri("/") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello World"); } }
@SpringBootTest @AutoConfigureMockMvc class MyMockMvcTests { @Test fun testWithMockMvc(@Autowired mvc: MockMvc) { mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk) .andExpect(MockMvcResultMatchers.content().string("Hello World")) } // Если Spring WebFlux находится в classpath, то можно управлять MVC-тестами с помощью WebTestClient @Test fun testWithWebTestClient(@Autowired webClient: WebTestClient) { webClient .get().uri("/") .exchange() .expectStatus().isOk .expectBody<String>().isEqualTo("Hello World") } }
ApplicationContext, то в качестве альтернативы рассмотрите вариант использования аннотации
@WebMvcTest.
Можно использовать WebTestClient с конечными точками Spring WebFlux так, как показано в следующем примере:
@SpringBootTest @AutoConfigureWebTestClient class MyMockWebTestClientTests { @Test void exampleTest(@Autowired WebTestClient webClient) { webClient .get().uri("/") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello World"); } }
@SpringBootTest @AutoConfigureWebTestClient class MyMockWebTestClientTests { @Test fun exampleTest(@Autowired webClient: WebTestClient) { webClient .get().uri("/") .exchange() .expectStatus().isOk .expectBody<String>().isEqualTo("Hello World") } }
Тестирование в имитационном окружении обычно проходит быстрее, чем при использовании заполненного контейнера сервлетов. Однако, поскольку имитирование происходит на уровне Spring MVC, код, который опирается на логику работы контейнера сервлетов более низкого уровня, не может быть непосредственно протестирован с помощью MockMvc.
Например, обработка ошибок в Spring Boot основана на поддержке "страницы ошибок", предусмотренной контейнером сервлетов. Это значит, что хотя и можно убедится, что уровень MVC генерирует и обрабатывает исключения, как это предполагается, напрямую убедится, что определенная кастомная страница ошибок отображается, нельзя. Если нужно протестировать эти низкоуровневые нюансы, можно запустить полностью работающий сервер, как описано в следующем разделе.
Тестирование при помощи работающего сервера
Если требуется запустить полноценно работающий сервер, то рекомендуем использовать случайные порты. Если вы используете @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT), доступный порт выбирается случайным образом при каждом запуске теста.
Аннотацию @LocalServerPort можно использоваться для того, чтобы внедрить фактически используемый порт в тест. Для удобства тесты, которым необходимо выполнять REST-вызовы к запущенному серверу, могут дополнительно через аннотацию @Autowire привязывать WebTestClient, который разрешает относительные ссылки на запущенный сервер и оснащен специальным APIом для проверки ответов, как показано в следующем примере:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class MyRandomPortWebTestClientTests { @Test void exampleTest(@Autowired WebTestClient webClient) { webClient .get().uri("/") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello World"); } }
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class MyRandomPortWebTestClientTests { @Test fun exampleTest(@Autowired webClient: WebTestClient) { webClient .get().uri("/") .exchange() .expectStatus().isOk .expectBody<String>().isEqualTo("Hello World") } }
WebTestClient можно использовать как для реальных серверов, так и для имитационных окружений.
Эта конфигурация требует наличия spring-webflux в classpath. Если вы не можете или не хотите добавлять webflux, Spring Boot также предусматривает возможность использования TestRestTemplate:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class MyRandomPortTestRestTemplateTests { @Test void exampleTest(@Autowired TestRestTemplate restTemplate) { String body = restTemplate.getForObject("/", String.class); assertThat(body).isEqualTo("Hello World"); } }
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class MyRandomPortTestRestTemplateTests { @Test fun exampleTest(@Autowired restTemplate: TestRestTemplate) { val body = restTemplate.getForObject("/", String::class.java) assertThat(body).isEqualTo("Hello World") } }
Настройка WebTestClient
Чтобы настроить бин WebTestClient, сконфигурируйте бин WebTestClientBuilderCustomizer. Любые подобные бины вызываются с помощью WebTestClient.Builder, который используется для создания WebTestClient.
Использование JMX
Поскольку фреймворк тестового контекста кэширует контекст, технология JMX по умолчанию отключена, чтобы предотвратить регистрацию идентичных компонентов в одном и том же домене. Если такому тесту требуется доступ к MBeanServer, подумайте о том, чтобы пометить его как грязный:
@SpringBootTest(properties = "spring.jmx.enabled=true") @DirtiesContext class MyJmxTests { @Autowired private MBeanServer mBeanServer; @Test void exampleTest() { assertThat(this.mBeanServer.getDomains()).contains("java.lang"); // ... } }
@SpringBootTest(properties = ["spring.jmx.enabled=true"]) @DirtiesContext class MyJmxTests(@Autowired val mBeanServer: MBeanServer) { @Test fun exampleTest() { assertThat(mBeanServer.domains).contains("java.lang") // ... } }
Использование метрик
Независимо от classpath, реестры счетчиков, за исключением хранящихся в памяти, не конфигурируются автоматически при использовании аннотации @SpringBootTest.
Если необходимо экспортировать метрики в другой бэкенд в рамках интеграционного теста, аннотируйте его с помощью @AutoConfigureMetrics.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ