Тестовий фреймворк Spring MVC Test, також відомий як MockMvc, забезпечує підтримку тестування програм Spring MVC. Він виконує повну обробку запитів Spring MVC через імітовані об'єкти-запити та об'єкти-відповідей замість працюючого сервера.

MockMvc можна використовувати окремо для виконання запитів та перевірки відповідей. Його також можна використовувати через WebTestClient, де MockMvc підключений як сервер для обробки запитів. Перевагою WebTestClient є можливість роботи з об'єктами вищого рівня замість сирих даних, а також можливість переходу до повноцінних наскрізних HTTP-тестів на реальному сервері з використанням того ж API-інтерфейсу тестування.

Короткий опис

Можна писати прості модульні тести для Spring MVC, створюючи екземпляри контролера, підключаючи до нього залежності та викликаючи його методи. Однак такі тести не перевіряють зіставлення запитів, прив'язку даних, перетворення повідомлень, перетворення типів, валідацію, а також не залучають жоден із допоміжних методів, анотованих @InitBinder, @ModelAttribute або @ExceptionHandler.

Фреймворк Spring MVC Test, також відомий як MockMvc, покликаний забезпечити більш повне тестування контролерів Spring MVC без працюючого сервера. Він робить це за допомогою виклику DispatcherServlet і передачі імітованих реалізацій API-інтерфейсу сервлетів з модуля spring-test, який відтворює повну обробку запитів Spring MVC без працюючого сервера.

MockMvc — це тестовий фреймворк на стороні сервера, який дозволяє перевіряти більшість функціональних можливостей програми Spring MVC за допомогою полегшених та цільових тестів. Можна використовувати його окремо для виконання запитів та перевірки відповідей, а також можна використовувати його через API-інтерфейс WebTestClient з підключеним MockMvc як сервер для обробки запитів.

Статичне імпортування

При використанні MockMvc безпосередньо для виконання запитів необхідно буде виконати статичне імпортування:

  • MockMvcBuilders.*

  • MockMvcRequestBuilders.*

  • MockMvcResultMatchers.*

  • MockMvcResultHandlers.*

Простий спосіб запам'ятати їх — здійснювати пошук за MockMvc*. Якщо ти користуєшся Eclipse, не забудь також додати вищевказані статичні члени до "вибраних" у налаштуваннях Eclipse.

При використанні MockMvc через WebTestClient тобі не знадобиться статичне імпортування. WebTestClient надає текучий (у значенні "плавний") API-інтерфейс без статичного імпортування.

Параметри налаштування

MockMvc може бути налаштований одним із двох способів. Один із них — вказати безпосередньо на контролери, які потрібно протестувати, та програмно налаштувати інфраструктуру Spring MVC. Другий — вказати на конфігурацію Spring при використанні фреймворку Spring MVC та інфраструктури контролера в ньому.

Щоб налаштувати MockMvc для тестування конкретного контролера, використовуй таке:

Java

class MyWebTests {
    MockMvc mockMvc;
    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }
    // ...
}
Kotlin

class MyWebTests {
    lateinit var mockMvc : MockMvc
    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
    }
    // ...
}

Або можна використовувати це налаштування при тестуванні через WebTestClient, який делегує повноваження тому ж засобу збирання, як це показано вище.

Щоб налаштувати MockMvc через конфігурацію Spring, використовуй таке:

Java

@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
    MockMvc mockMvc;
    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }
    // ...
}
Kotlin

@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {
    lateinit var mockMvc: MockMvc
    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }
    // ...
}

Або також можна використовувати це налаштування при тестуванні через WebTestClient, який делегує повноваження тому ж засобу збирання, як це показано вище.

Який варіант налаштування слід використовувати?

У webAppContextSetup завантажується реальна конфігурація Spring MVC, що дозволяє отримати більш повний інтеграційний тест. Оскільки фреймворк TestContext кешує завантажену конфігурацію Spring, він допомагає здійснювати швидке виконання тестів навіть якщо ти вводиш все більше тестів до свого тестового комплекту. Ба більше, можна впроваджувати імітовані служби до контролерів за допомогою конфігурації Spring, щоб зосередитись на тестуванні вебрівня. У наступному прикладі імітована служба оголошена за допомогою Mockito:


<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="org.example.AccountService"/>
</bean>

Потім можна впровадити імітовану службу до тесту для налаштування та перевірки ваших очікуваних подій, як показано в наступному прикладі:

Java

@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {
    @Autowired
    AccountService accountService;
    MockMvc mockMvc;
    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
    // ...
}
Kotlin

@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {
    @Autowired
    lateinit var accountService: AccountService
    lateinit mockMvc: MockMvc
    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }
    // ...
} 

Налаштування standaloneSetup, з іншого боку, трохи більше підходить для модульного тестування. Вона тестує один контролер за один раз. Можна вручну впровадити імітовані залежності до контролера, і це не вимагатиме завантаження конфігурації Spring. Такі тести більше орієнтовані стиль і дозволяють зрозуміти, який контролер тестується, чи потрібна якась конкретна конфігурація Spring MVC до роботи тощо. Налаштування standaloneSetup також є дуже зручним способом написання спеціальних тестів для перевірки певної логіки роботи або налагодження проблеми.

Як і в більшості суперечок на тему "інтеграційне тестування проти модульного тестування", тут немає правильної чи неправильної відповіді. Однак, використання standaloneSetup передбачає необхідність додаткових тестів з використанням webAppContextSetup для перевірки конфігурації Spring MVC. Крім того, можна написати всі тести з використанням webAppContextSetup, щоб завжди проводити тестування на реальній конфігурації Spring MVC.

Особливості налаштування

Незалежно від того, який засіб збірки з MockMvc ти використовуєш, всі реалізації MockMvcBuilder нададуть деякі спільні та вкрай корисні функції. Наприклад, ти зможеш оголосити заголовок Accept для всіх запитів і прийняти статус 200, а також заголовок Content-Type у всіх відповідях, як показано нижче:

Java

// статичний імпорт MockMvcBuilders.standaloneSetup
MockMvc mockMvc = standaloneSetup(new MusicController())
    .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build();
Kotlin
// Неможливо в Kotlin,поки не буде виправлено

До того ж, сторонні фреймворки (і програми) можуть попередньо упаковувати інструкції з налаштування, наприклад, MockMvcConfigurer. Spring Framework має одну з таких вбудованих реалізацій, яка допомагає зберігати та повторно використовувати HTTP-сесію у всіх запитах. Ти можеш використовувати її таким чином:

Java

// статичний імпорт SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
    .apply(sharedHttpSession())
    .build();
// Імпортуємо mockMvc для виконання запитів...
Kotlin
// Неможливо в Kotlin,поки не буде виправлено

Див. javadoc ConfigurableMockMvcBuilder для ознайомлення зі списком усіх можливостей засобу збирання з MockMvc або використовуй IDE для вивчення доступних опцій.

Виконання запитів

У цьому розділі показано, як використовувати MockMvc окремо для виконання запитів та перевірки відповідей. Якщо ти використовуєш MockMvc через WebTestClient, будь ласка, звернися до відповідного розділу написання тестів.

Щоб виконати запити, які використовують будь-який метод HTTP, спирайся на приклад, наведений нижче:

Java

// статичний імпорт MockMvcRequestBuilders.*.
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
Kotlin

import org.springframework.test.web.servlet.post
mockMvc.post("/hotels/{id}", 42) {
    accept = MediaType.APPLICATION_JSON
}

Також можна виконувати запити на завантаження файлів, які на внутрішньому рівні використовують MockMultipartHttpServletRequest, щоб фактично не здійснювати синтаксичний аналіз багатокомпонентного запиту. Швидше, доведеться налаштувати його так, як це показано в наступному прикладі:

Java
mockMvc.perform(multipart("/doc").file("a1" , "ABC".getBytes("UTF-8")));
Kotlin

import org. springframework.test.web.servlet.multipart
mockMvc.multipart("/doc") {
    file("a1", "ABC".toByteArray(charset("UTF8")))
}

Можна встановити параметри запиту в стилі шаблону URI-ідентифікатора, як показано в наступному прикладі:

Java
mockMvc.perform(get ("/hotels?thing={thing}", "somewhere"));
Kotlin
mockMvc .get("/hotels?thing={thing}", "somewhere")

Також можна додати параметри запиту сервлета, які є або параметри запиту, або параметри форми, як це показано в наступному прикладі:

Java
mockMvc.perform(get("/hotels").param("thing", " somewhere"));
Kotlin

import org.springframework.test.web.servlet.get
mockMvc.get("/hotels") {
    param("thing", "somewhere")
}

Якщо код програми використовує параметри запиту сервлета і не перевіряє рядок запиту явним чином (що найчастіше і відбувається), то не має значення, який варіант ти використовуєш. Однак слід пам'ятати, що параметри запиту, надані за допомогою шаблону URI-ідентифікатора, декодуються, тоді як параметри запиту, надані через метод param(…), як очікується, вже декодовані.

У більшості випадків краще не вказувати в URI-ідентифікаторі запиту шлях до контексту та шлях до сервлета. Якщо вам потрібно проводити тестування з повним URI-ідентифікатором запиту, не забудь встановити contextPath та servletPath відповідним чином, щоб зіставлення запитів працювало, як показано в наведеному нижче прикладі:

Java
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
Kotlin

import org.springframework.test.web.servlet.get
mockMvc.get("/ app/main/hotels/{id}") {
    contextPath = "/app"
    servletPath = "/main"
}

У наведеному вище прикладі було б незручно встановлювати contextPath та servletPath при кожному виконаному запиті. Натомість можна встановити властивості запиту за замовчуванням, як показано в наступному прикладі:

Java

class MyWebTests {
    MockMvc mockMvc;
    @BeforeEach
    void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON)).build();
    }
}
Kotlin
// Неможливо в Kotlin,поки не буде виправлено

Попередні властивості впливають на кожен запит, який виконується через екземпляр MockMvc. Якщо ця властивість також зазначена в даному запиті, вона перевизначає значення за замовчуванням. Тому метод HTTP та URI-ідентифікатор у запиті за замовчуванням не мають значення, тому що їх потрібно вказувати для кожного запиту.

Визначення очікуваних подій

Можна визначити очікувані події, додавши один або кілька викликів andExpect(..) після виконання запиту, як показано в наведеному нижче прикладі. Якщо будь-яка одна очікувана подія не відбудеться, інші очікувані події не будуть підтверджені. title">Java


// статичний імпорт MockMvcRequestBuilders.*. and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
Kotlin

import org.springframework.test.web.servlet.get
mockMvc.get("/accounts/1").andExpect {
    status { isOk() }
}

Ти можеш визначати кілька очікуваних подій, додавши andExpectAll(..) після виконання запиту, як показано в наведеному нижче прикладі. На відміну від andExpect(..), andExpectAll(..) гарантує, що всі зазначені очікувані події будуть підтверджені, а всі, що не відбулися, будуть відстежені та повідомлені.

Java

// статичний імпорт MockMvcRequestBuilders.*. and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpectAll(
    status().isOk(),
    content().contentType("application/json;charset=UTF-8"));

MockMvcResultMatchers.* вказує низку очікуваних подій, деякі з яких далі вкладені в докладніші очікувані події.

Очікувані події поділяються на дві загальні категорії. Перша категорія тверджень перевіряє властивості відповіді (наприклад, статус відповіді, заголовки та вміст). Це найважливіші результати, які можна підтверджувати.

Друга категорія тверджень виходить за межі відповіді. Ці твердження дозволяють перевіряти специфічні аспекти Spring MVC, такі як те, який метод контролера обробив запит, чи було викликано та оброблено виняток, який вміст моделі, яке уявлення було обрано, які flash-атрибути були додані тощо. Вони також дозволяють перевіряти специфічні аспекти сервлетів, такі як атрибути запиту та сесії.

Наступний тест підтверджує, що прив'язка або валідація не вдалися:

Java

mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin

import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andExpect {
    status { isOk() }
    model {
        attributeHasErrors("person")
    }
}

Часто при написанні тестів корисно вивантажити результати виконаного запиту. Зробити це можна в такий спосіб, де print() — це статичний імпортований елемент з MockMvcResultHandlers:

Java

mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person")); 
Kotlin

import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andDo {
        print()
    }.andExpect {
        status { isOk() }
        model {
            attributeHasErrors("person")
        }
    }

Доки обробка запиту не викличе необроблений виняток, метод print() виводитиме всі доступні дані результату в System.out. Існує також метод log() і два додаткові варіанти методу print(), один з яких приймає OutputStream, а інший — Writer. Наприклад, виклик print(System.err) виводить дані результату в System.err, а виклик print(myWriter) виводить дані результату до спеціального методу запису (writer). Якщо потрібно, щоб дані результату не виводилися, а записувалися в журнал, можна викликати метод log(), який записує дані результату у вигляді одного повідомлення DEBUG у категорії журналування org.springframework.test.web.servlet.result.

У деяких випадках вам може знадобитися прямий доступ до результату та перевірити те, що неможливо перевірити іншим способом. Це можна зробити, якщо додати .andReturn() після всіх інших очікуваних подій, як показано в наступному прикладі:

Java

MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
Kotlin

var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn()
// ...

Якщо всі тести повторюють одні й ті ж очікувані події, то можна встановити загальні очікувані події один раз при створенні екземпляра MockMvc, як показано в наступному прикладі:

Java

standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()
Kotlin
// Неможливо в Kotlin,поки не буде виправлено

Зверни увагу, що загальні очікувані події застосовуються завжди і не можуть бути перевизначені без створення окремого екземпляра MockMvc.

Якщо вміст JSON-відповіді містить гіпермедійні посилання, створені за допомогою Spring HATEOAS, ти можеш перевірити отримані посилання за допомогою виразів JsonPath, як показано в наведеному нижче прикладі:

Java

mockMvc.perform(get("/people").accept (MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people")));
Kotlin

mockMvc.get("/people") {
    accept(MediaType.APPLICATION_JSON)
}.andExpect {
    jsonPath("$.links[?(@.rel == 'self')].href") {
        value("http://localhost:8080/people")
    }
}

Якщо вміст XML-відповіді містить гіпермедійні посилання, створені за допомогою Spring HATEOAS , ти можеш перевірити отримані посилання за допомогою виразів XPath:

Java

Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string(" http://localhost:8080/people"));
Kotlin

val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
    accept(MediaType.APPLICATION_XML)
}.andExpect {
    xpath("/person/ns:link[@rel='self']/@href", ns) {
        string("http://localhost:8080/people")
    }
}

Асинхронні запити

У цьому розділі показано, як використовувати MockMvc окремо для тестування асинхронної обробки запитів. При використанні MockMvc через WebTestClient немає нічого особливого в тому, щоб змусити працювати асинхронні запити, оскільки WebTestClient автоматично робить те, що описано в цьому розділі.

Асинхронні запити Servlet 3.0, які підтримуються в Spring MVC, працюють шляхом виходу з потоку контейнера сервлетів, тим самим дозволяючи додатку обчислити відповідь асинхронно, після чого виконується асинхронна диспетчеризація для завершення обробки в потоці контейнера сервлетів.

У Spring MVC Test асинхронні запити можна протестувати, якщо спочатку підтвердити створене асинхронне значення, а потім вручну виконати асинхронну диспетчеризацію і, нарешті, перевірити відповідь. Нижче наведено приклад тесту для методів контролера, які повертають DeferredResult, Callable або реактивний тип, такий як Mono з Reactor:

Java

// статичний імпорт MockMvcRequestBuilders.*. and MockMvcResultMatchers.*
@Test
void test() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(get("/path"))
            .andExpect(status().isOk()) 
            .andExpect(request().asyncStarted()) 
            .andExpect(request() .asyncResult("body")) 
            .andReturn();
    this.mockMvc.perform(asyncDispatch(mvcResult)) 
            .andExpect(status().isOk()) 
            .andExpect(content().string("body")); }
  1. Статус перевірки відповіді залишається незмінним
  2. Повинна розпочатися асинхронна обробка
  3. Чекаємо та підтверджуємо результат асинхронної обробки
  4. Вручну виконуємо асинхронну диспетчеризацію (оскільки немає запущеного контейнера)
  5. Перевіряємо остаточну відповідь
Kotlin

@Test
fun test() {
    var mvcResult = mockMvc.get("/path").andExpect {
        status { isOk() } 
        request { asyncStarted() } 
        // TODO Remove unused generic parameter request { asyncResult<Nothing> ("body") } 
    }.andReturn()
    mockMvc.perform(asyncDispatch(mvcResult)) 
            .andExpect {
                status { isOk() } 
                content().string("body ")
            }
}
  1. Статус перевірки відповіді залишається незмінним
  2. Повинна розпочатися асинхронна обробка
  3. Чекаємо та підтверджуємо результат асинхронної обробки
  4. Вручну виконуємо асинхронну диспетчеризацію (оскільки немає запущеного контейнера)
  5. Перевіряємо остаточну відповідь

Потокові відповіді

Найкращим способом тестування потокових відповідей, таких як події, що посилаються сервером (Server-Sent Events), є WebTestClient, який можна використовувати як тестовий клієнт, щоб підключити до екземпляра MockMvc для виконання тестів на контролерах Spring MVC без сервера. Наприклад:

Java

WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
        .uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType("text/event-stream")
        .returnResult(Person.class);
// Використовуємо StepVerifier з Project Reactor для тестування потокової відповіді
StepVerifier.create(exchangeResult.getResponseBody())
        .expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
        .expectNextCount(4)
        .consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
        .thenCancel()
        .verify();
            lt(Person.class);

WebTestClient також може підключатися до реального сервера та виконувати повні наскрізні інтеграційні тести. Подібне так само підтримується в Spring Boot, де ти можеш тестувати запущений сервер.

Реєстрація фільтрів

При налаштуванні екземпляра MockMvc можна зареєструвати один або кілька екземплярів Filter сервлетів, як показано в наступному прикладі:

Java
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
Kotlin
// Неможливо в Kotlin,поки не буде виправлено

Зареєстровані фільтри викликаються через MockFilterChain з spring-test, а останній фільтр делегується DispatcherServlet.

MockMvc проти наскрізних тестів

MockMvc побудований на основі імітаційних реалізацій Servlet API з модуля spring-test і не залежить від запущеного контейнера. Тому існують деякі відмінності в порівнянні з повними наскрізними інтеграційними тестами з реальним клієнтом і реальним сервером. Найпростіший спосіб зрозуміти це — почати з порожнього запиту MockHttpServletRequest. Що додається до нього, те стає запитом. Речі, які можуть застати тебе зненацька: за замовчуванням контекстного шляху немає; немає cookie файлу jsessionid; відсутня перенаправлення, помилки або асинхронна диспетчеризація; і, отже, фактично відсутній JSP-рендерінг (візуалізація). Натомість "перенаправлені" та "переадресовані" URL-адреси зберігаються в MockHttpServletResponse, після чого їх можна підтвердити очікуваними подіями.

Це означає, що якщо використовується JSP, то можна перевірити JSP-сторінку, на яку було перенаправлено запит, але HTML не візуалізуватиметься. Іншими словами, JSP не викликається. Зверни увагу, однак, що будь-які інші технології рендерингу, які не покладаються на перенаправлення, такі як Thymeleaf та Freemarker, передають HTML до тіла відповіді, як і очікується. Те саме справедливо для рендерингу JSON, XML та інших форматів за допомогою методів, анотованих @ResponseBody.

До того ж, ти можеш подумати над повною підтримкою наскрізного інтеграційного тестування Spring Boot за допомогою анотації @SpringBootTest. Див. " Довідковий посібник із Spring Boot".

У кожного підходу є свої плюси та мінуси. Опції, що надаються у Spring MVC Test, знаходяться у різних місцях на шкалі від класичного модульного тестування до повного інтеграційного тестування. Звісно, жодна з опцій Spring MVC Test не підпадає під категорію класичного модульного тестування, але вони трохи ближче до неї. Наприклад, можна ізолювати вебрівень, впроваджуючи імітовані служби в контролери, і в цьому випадку вебрівень тестуватиметься лише через DispatcherServlet, але з реальною конфігурацією Spring, тому що можна буде тестувати рівень доступу до даних окремо від рівнів, що знаходяться вище. До того ж, можна використовувати автономну установку, зосередившись на одному контролері за раз і вручну забезпечивши конфігурацію, необхідну для його роботи. Ще одна важлива відмінність при використанні тесту у фреймворку Spring MVC Test полягає в тому, що концептуально такі тести є серверними, тому у тебе є можливість перевірити, який дескриптор був використаний, був виняток оброблено за допомогою HandlerExceptionResolver, який вміст моделі, які були помилки прив'язування та інші деталі. Це означає, що написати очікувані події стає легше, оскільки сервер не є непрозорою скринькою, як це буває при тестуванні через фактичний HTTP-клієнт. Загалом перевага класичного модульного тестування полягає в тому, що таке тестування легше писати, обґрунтовувати та налагоджувати, але воно не забирає потреби у повноцінних інтеграційних тестах. Водночас важливо не забувати, що відповідь — це найважливіше, що необхідно перевіряти. Одним словом, тут є простір для кількох стилів та стратегій тестування навіть у рамках одного проєкту.

Додаткові приклади

Власні тести фреймворку включають безліч тестових прикладів, покликаних показати, як використовувати MockMvc окремо або через WebTestClient. Переглянь ці приклади, щоб почерпнути нові ідеї.