Как тестируют Spring MVC или REST контроллеры?

Ответ

Для тестирования контроллеров Spring используется комбинация JUnit 5, Spring Test модуля и Mockito.

1. Тестирование слоя контроллера изолированно (@WebMvcTest): Загружается только веб-слой контекста, все зависимости контроллера мокаются.

   @WebMvcTest(UserController.class) // Загружаем только контроллер и MVC инфраструктуру
   class UserControllerSliceTest {

       @Autowired
       private MockMvc mockMvc; // Главный инструмент для имитации запросов

       @MockBean
       private UserService userService; // Зависимость контроллера заменяется mock-объектом

       @Test
       void getUserById_ShouldReturnUser() throws Exception {
           // Given
           User mockUser = new User(1L, "Alice");
           when(userService.findById(1L)).thenReturn(Optional.of(mockUser));

           // When & Then
           mockMvc.perform(get("/api/users/1")
                   .accept(MediaType.APPLICATION_JSON))
                  .andExpect(status().isOk())
                  .andExpect(jsonPath("$.id").value(1L))
                  .andExpect(jsonPath("$.name").value("Alice"));
           verify(userService).findById(1L); // Проверяем вызов сервиса
       }
   }

2. Интеграционное тестирование (@SpringBootTest): Загружается полный контекст приложения. Используется TestRestTemplate или WebTestClient для реальных HTTP-вызовов.

   @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
   class UserControllerIntegrationTest {

       @Autowired
       private TestRestTemplate restTemplate;

       @Test
       void getUserById_Integration() {
           ResponseEntity<User> response = restTemplate.getForEntity("/api/users/1", User.class);
           assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
           assertThat(response.getBody().getName()).isNotNull();
       }
   }

Ключевые аннотации: @WebMvcTest (для срезов), @SpringBootTest (для интеграции), @MockBean (для моков в контексте Spring).

Ответ 18+ 🔞

Да ты посмотри, какая хуйня творится в мире тестирования контроллеров! Ну, типа, чтобы не писать говнокод, который потом развалится от первого чиха, надо их проверять. И для этого, блядь, есть целая связка: JUnit 5, этот Spring Test и Mockito, который всех на блюдечке подвезёт.

1. Тестируем контроллер, как будто он один такой умный в вакууме (@WebMvcTest): Тут поднимается только веб-слой, всё остальное — в пизду, заменяется заглушками.

   @WebMvcTest(UserController.class) // Тащим в память только этот контроллер и всю его MVC-обвязку, ебать
   class UserControllerSliceTest {

       @Autowired
       private MockMvc mockMvc; // Эта штука будет притворяться браузером и слать запросы, хитрая жопа

       @MockBean
       private UserService userService; // А сервис наш — муляж, мартышлюшка, которая будет говорить то, что мы скажем

       @Test
       void getUserById_ShouldReturnUser() throws Exception {
           // Допустим
           User mockUser = new User(1L, "Alice");
           when(userService.findById(1L)).thenReturn(Optional.of(mockUser)); // Говорим муляжу: «Вот, на, верни это, когда спросят»

           // Делаем и проверяем
           mockMvc.perform(get("/api/users/1")
                   .accept(MediaType.APPLICATION_JSON))
                  .andExpect(status().isOk()) // Ждём, что статус 200, а не 404 или 500, ёпта
                  .andExpect(jsonPath("$.id").value(1L)) // И что в JSON поле id равно единице
                  .andExpect(jsonPath("$.name").value("Alice")); // И имя Alice, а не Вася, блядь
           verify(userService).findById(1L); // А тут мы, как хипстеры, проверяем, что сервис вообще вызвали, а не проёбали
       }
   }

2. А теперь по-взрослому, интеграционное тестирование (@SpringBootTest): Тут поднимается ВСЁ, весь контекст, как на боевом сервере. И мы долбимся в него по-настоящему, через HTTP. Терпения ебать ноль, но надо.

   @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // Поднимаем приложение на случайном порту, чтобы не пересекаться
   class UserControllerIntegrationTest {

       @Autowired
       private TestRestTemplate restTemplate; // Это наш грузовик, который будет возить HTTP-запросы туда-сюда

       @Test
       void getUserById_Integration() {
           ResponseEntity<User> response = restTemplate.getForEntity("/api/users/1", User.class); // Поехали, сука!
           assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); // Надеемся, что не 500 Internal Server Error
           assertThat(response.getBody().getName()).isNotNull(); // И что имя у пользователя есть, а не null, блядь
       }
   }

Главные аннотации, без которых нихуя не получится: @WebMvcTest (когда хочешь только кусочек), @SpringBootTest (когда готов к овердохуища нагрузки), @MockBean (чтобы подсунуть Spring'у свою подставную суку вместо настоящего бина). Вот и вся магия, в рот меня чих-пых!