Команда Spring АйО перевела статью о том, как правильно подходить к тестированию веб-контроллеров в Spring Boot приложениях, чтобы не делать лишнюю работу, но и не упускать важных аспектов процесса тестирования.
Тестирование контроллеров в Spring Boot приложениях похоже на исполнение циркового трюка на удержание равновесия.
Вам хочется быстрых, точных тестов, которые находят реальные проблемы, не пропускают критические ошибки и при этом не замедляют общий прогон тестов. Слишком часто разработчики попадают в одну из двух ловушек: они либо пишут простые юнит-тесты с использованием Mockito и JUnit, либо сразу берутся за полноразмерный @SpringBootTest
.
Ни то, ни другое не дает желаемого результата. В этой статье я покажу вам почему — и как —@WebMvcTest
обеспечивает идеальный баланс, сохраняя как семантику HTTP, так и легкость и мощность ваших тестов.
Ловушки простых юнит-тестов
Давайте начнем с самого распространенного решения: простой юнит-тест для вашего контроллера с использованием JUnit и Mockito. Вы чувствуете искушение воспользоваться таким способом — сделать mock сервисного слоя, вызвать метод контроллера напрямую и применить assert
к результату. Быстро, изолированно, готово. Верно?
Не совсем. Когда вы тестируете контроллер таким способом, вы теряете нечто критически важное: семантику HTTP запроса. Контроллеры в Spring Boot — это не просто какие-то классы, они являются точками входа в ваше веб приложение, обрабатывающими запросы, ответы, коды статуса, заголовки и безопасность.
Обход HTTP-слоя в обычных юнит-тестах означает, что вы не тестируете, как ваш контроллер функционирует в реальных условиях.
Вот что вы пропускаете:
-
Маппинг запросов: вы уверены, что
/api/users/{id}
действительно указывает на ваш метод? -
Коды статуса: какой код возвращает bad request, 400 или случайный 200?
-
Заголовки: правильно ли вы посылаете
Content-Type
или кастомизированные заголовки? -
Безопасность: всегда ли выполняется ваше правило
@PreAuthorize
?
Использование Mockito для всего подряд может помочь проверить логику вашего метода, но теряет то самое связующее вещество, которое делает контроллер контроллером. Вы тестируете пустую болванку, а не реальный продукт.
Почему @SpringBootTest — это перебор
С другой стороны, некоторые разработчики идут слишком далеко в противоположном направлении и используют @SpringBootTest
. Эта аннотация приводит в действие все ваше Spring Boot приложение полностью — базу данных, сервисы, бины, вообще все.
Конечно, она протестирует ваш контроллер в “реальном” окружении, вместе с семантикой HTTP при помощи TestRestTemplate
или WebTestClient
. Но это все равно, что использовать кувалду, чтобы расколоть грецкий орех.
А проблема вот в чем:
-
Медленный запуск: загрузка всего контекста приложения может занять 20-45 секунд, замедляя работу вашего набора тестов.
-
Чрезмерный охват: вы тестируете контроллер, а не базу данных и не имеющие отношения к контроллеру бины.
-
Сложность: отладка ошибок превращается в поиск иголки в стогу сена по всем слоям приложения.
Аннотация @SpringBootTest
прекрасна для интеграционных тестов, для которых вам нужно все приложение, но для тестирования контроллеров? Это перебор. Вам нужна точечность, скорость и достаточная реалистичность — без лишнего багажа.
Золотая середина: @WebMvcTest
В чат входит @WebMvcTest
— то самое решение, которое вам нужно для тестирования Spring Boot контроллеров.
Эта аннотация загружает только веб слой (контроллеры, фильтры и MVC инфраструктуру) заменяя остальную часть приложения на mock-и.
Комментарий от редакции Spring АйО
Важный момент: mock-объекты для других частей приложения не создаются автоматически. Бины из остальной части приложения просто не будут загружены и если контроллер зависит от этих бинов, тест упадёт с ошибкой, потому что Spring не смог создать контроллер. Чтобы всё заработало, разработчик должен сам замокать все зависимости контроллера, как показано в примерах ниже.
Она работает быстро, дает необходимый фокус и, что важнее всего, сохраняет HTTP семантику с помощью MockMvc от Spring.
Вот почему это лучший выбор:
1. Mock-и для HTTP семантики
Работая с MockMvc, вы имитируете реальные HTTP запросы — GET, POST, PUT, DELETE, какие угодно.
Вы не вызываете методы напрямую, а только обращаетесь к эндпоинтам, как как это делает клиентское приложение.
Это позволяет вам проверить:
-
Маппинг URL-ов: является ли
/api/users
правильным путем? -
Тело запроса: правильно ли десериализуются входные данные в формате JSON?
-
Тело ответа: такие ли выходные данные вы ожидали?
2. Статус коды и заголовки
MockMvc дает вам полный контроль, позволяющий проверить параметры, свойственные HTTP:
-
Статус:
expect(status().isOk())
илиexpect(status().isForbidden())
. -
Заголовки:
expect(header().string("X-Custom-Header", "value"))
. -
Тип содержимого:
expect(content().contentType(MediaType.APPLICATION_JSON))
.
Больше не нужно гадать, устанавливает ли ваш контроллер 201 Created и не забывает ли он про заголовок — @WebMvcTest
позаботится обо всем.
3. Security Testing
У вас подключена Spring Security?
@WebMvcTest
готова к таким вызовам. Добавьте .with(user("testuser").roles("USER"))
к вашему запросу и тестируйте:
Аутентификация: отклонит ли эндпоинт запрос без аутентификации?
Авторизация: правильно ли работает ролевая модель @PreAuthorize("hasRole('ADMIN')")
?
Все это можно сделать простым юнит-тестом — а при использовании @SpringBootTest
такие тесты ужасно медленные.
4. Скорость и фокус
Если загружать только слой MVC и зависимости для mock-ов (например, сервисы с @MockitoBean
), тесты, использующие @WebMvcTest
, остаются достаточно легкими. Здесь нет ни подключений к базе данных, ни посторонних бинов — только сам контроллер и его поведение с точки зрения HTTP.
Пример
Давайте посмотрим, как работает @WebMvcTest
. Представим себе простой контроллер:
@RestController @RequestMapping("/api/users") public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.findById(id); return ResponseEntity.ok(user); } }
Вот как его можно протестировать с использованием @WebMvcTest
:
@WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockitoBean private UserService userService; @Test void getUser_ReturnsUser_WhenFound() throws Exception { // Arrange User user = new User(1L, "Alice"); when(userService.findById(1L)).thenReturn(user); // Act & Assert mockMvc.perform(get("/api/users/1") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.name").value("Alice")); } }
Что здесь происходит?
-
@WebMvcTest
загружает толькоUserController
и MVC компоненты. -
@MockitoBean
имитируетUserService
, оставляя тест изолированным. -
MockMvc
посылает GET запрос к/api/users/1
и проверяет статус, тип содержимого и ответ в формате JSON.
Быстро, сфокусированно и с учетом HTTP семантики. Никакого полного запуска всего приложения, никакого игнорирования веб-слоя.
Советы по успешному использованию @WebMvcTest
-
Mock зависимости: используйте
@MockBean
для сервисов или репозиториев, необходимых вашему контроллеру. -
Настройки безопасности: добавьте
@WithMockUser
или кастомизированныйSecurityMockMvcRequestPostProcessors
для тестов аутентификации. -
Обработка ошибок: тестируйте ответы 400, 404 или 500, вводя неверные данные или имитируя ошибки.
Пусть ваши тесты будут легкими: чтобы не загружать ненужные контроллеры, задайте целевой класс (например, @WebMvcTest(UserController.class)
).
Заключение: тестируйте с умом, а не через усилия
Тестирование контроллеров Spring Boot приложения не обязательно должно быть компромиссом.
Простые юнит-тесты с использованием Mockito не воспроизводят HTTP семантику, оставляя прорехи в покрытии, @SpringBootTest
заставляет использовать огромное количество ресурсов, которые вам не нужны. @WebMvcTest
становится золотой серединой — имитирует HTTP, сохраняет настоящие эндпоинты и предоставляет только необходимый контекст, чтобы протестировать важные моменты: коды статуса, заголовки, безопасность и ответы.
В следующий раз, когда вы будете писать тесты контроллеров, не впадайте в крайности и сразу берите @WebMvcTest
. Ваша команда будет вам благодарна. Приятного тестирования.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
ссылка на оригинал статьи https://habr.com/ru/articles/892194/
Добавить комментарий