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
:
import org.springframework.boot.test.context.SpringBootTest;
@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
використовується на
додаток до основної конфігурації програми.
Тестовий фреймворк Spring кешує контексти додатків між тестами. Тому, поки тести використовують одну й ту саму конфігурацію (незалежно від того, яким чином її було виявлено), потенційно ресурсомісткий процес завантаження контексту відбувається лише один раз.
Виключення тестової конфігурації
Якщо програма використовує сканування компонентів (наприклад, при
використанні анотацій @SpringBootApplication
або @ComponentScan
), можна виявити, що
високорівневі класи конфігурації, які були створені тільки для певних тестів, випадково перехоплюються всюди.
Аннотацію @TestConfiguration
можна використовувати у внутрішньому тестовому класі для налаштування
первинної конфігурації. Якщо анотацією @TestConfiguration
позначає високорівневий клас, вона вказує, що
класи в src/test/java
не повинні перехоплюватися під час сканування. Потім можна імпортувати цей клас у
явному вигляді, коли це необхідно, як показано в наступному прикладі:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {
@Test
void exampleTest() {
// ...
}
}
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Import
@SpringBootTest
@Import(MyTestsConfiguration::class)
class MyTests {
@Test
fun exampleTest() {
// ...
}
}
@ComponentScan
використовується безпосередньо
(тобто не через анотацію @SpringBootApplication
), необхідно зареєструвати
TypeExcludeFilter
разом з нею.
Використання аргументів програми
Якщо програма приймає аргументи, можна за допомогою анотації @SpringBootTest
впровадити їх через атрибут args
.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@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");
}
}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.test.context.SpringBootTest
@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
, як показано в
наступному прикладі:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@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");
}
}
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@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 так, як показано в
наступному прикладі:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {
@Test
void exampleTest(@Autowired WebTestClient webClient) {
webClient
.get().uri("/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World");
}
}
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@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 для перевірки відповідей, як показано в
наступному прикладі:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;
@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");
}
}
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
@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
:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {
@Test
void exampleTest(@Autowired TestRestTemplate restTemplate) {
String body = restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.client.TestRestTemplate
@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
, подумай про те, щоб позначити його
як
брудний:
import javax.management.MBeanServer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {
@Autowired
private MBeanServer mBeanServer;
@Test
void exampleTest() {
assertThat(this.mBeanServer.getDomains()).contains("java.lang");
// ...
}
}
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext
import javax.management.MBeanServer
@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
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ