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
.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ