← Назад к вопросам

Как обращался к Spring Controller при тестировании

2.3 Middle🔥 251 комментариев
#Spring Framework#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Тестирование Spring Controllers

Тестирование контроллеров требует разных подходов в зависимости от того, нужно ли тестировать HTTP слой или бизнес-логику.

1. MockMvc — тестирование HTTP слоя

Базовый setup

@WebMvcTest(UserController.class)  // Загружает только контроллер и его зависимости
public class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testGetUser() throws Exception {
        // Arrange
        User user = new User(1L, "John", "john@example.com");
        when(userService.getUserById(1L)).thenReturn(user);
        
        // Act & Assert
        mockMvc.perform(get("/api/v1/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"))
            .andExpect(jsonPath("$.email").value("john@example.com"));
        
        verify(userService).getUserById(1L);
    }
}

Тестирование разных HTTP методов

@WebMvcTest(UserController.class)
public class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testCreateUser() throws Exception {
        // POST запрос
        mockMvc.perform(post("/api/v1/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""
                {
                    "name": "Jane",
                    "email": "jane@example.com"
                }
                """))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").exists());
    }
    
    @Test
    void testUpdateUser() throws Exception {
        // PUT запрос
        mockMvc.perform(put("/api/v1/users/1")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""
                {
                    "name": "John Updated",
                    "email": "john.updated@example.com"
                }
                """))
            .andExpect(status().isOk());
    }
    
    @Test
    void testDeleteUser() throws Exception {
        // DELETE запрос
        mockMvc.perform(delete("/api/v1/users/1"))
            .andExpect(status().isNoContent());
    }
    
    @Test
    void testPartialUpdate() throws Exception {
        // PATCH запрос
        mockMvc.perform(patch("/api/v1/users/1")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""
                {
                    "name": "John Patched"
                }
                """))
            .andExpect(status().isOk());
    }
}

2. Тестирование параметров запроса

@WebMvcTest(UserController.class)
public class UserControllerParametersTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testGetUsersWithPagination() throws Exception {
        // Query параметры
        mockMvc.perform(get("/api/v1/users")
            .param("page", "0")
            .param("size", "10")
            .param("sort", "name,asc"))
            .andExpect(status().isOk());
        
        // verify был вызван
        verify(userService).findAllUsers(any(Pageable.class));
    }
    
    @Test
    void testGetUsersByStatus() throws Exception {
        // Multiple параметры
        mockMvc.perform(get("/api/v1/users")
            .param("status", "ACTIVE")
            .param("role", "ADMIN"))
            .andExpect(status().isOk());
    }
    
    @Test
    void testGetUserWithPathVariable() throws Exception {
        // Path variable
        mockMvc.perform(get("/api/v1/users/john-doe"))
            .andExpect(status().isOk());
    }
}

3. Тестирование заголовков и аутентификации

@WebMvcTest(UserController.class)
public class UserControllerSecurityTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testAuthorizationHeader() throws Exception {
        mockMvc.perform(get("/api/v1/users/1")
            .header("Authorization", "Bearer token123"))
            .andExpect(status().isOk());
    }
    
    @Test
    void testCustomHeaders() throws Exception {
        mockMvc.perform(get("/api/v1/users/1")
            .header("X-Request-ID", "req-123")
            .header("X-Trace-ID", "trace-456"))
            .andExpect(status().isOk())
            .andExpect(header().exists("X-Response-Time"));
    }
    
    @Test
    @WithMockUser(username = "admin", roles = "ADMIN")
    void testWithMockUser() throws Exception {
        // Тест с аутентифицированным пользователем
        mockMvc.perform(get("/api/v1/users"))
            .andExpect(status().isOk());
    }
    
    @Test
    void testUnauthorized() throws Exception {
        mockMvc.perform(get("/api/v1/users/1"))
            .andExpect(status().isUnauthorized());
    }
}

4. Тестирование ошибок и исключений

@WebMvcTest(UserController.class)
public class UserControllerExceptionTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testUserNotFound() throws Exception {
        when(userService.getUserById(999L))
            .thenThrow(new UserNotFoundException("User not found"));
        
        mockMvc.perform(get("/api/v1/users/999"))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.error").value("User not found"));
    }
    
    @Test
    void testInvalidInput() throws Exception {
        mockMvc.perform(post("/api/v1/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""
                {
                    "name": "",
                    "email": "invalid-email"
                }
                """))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.errors").isArray());
    }
    
    @Test
    void testValidationError() throws Exception {
        mockMvc.perform(post("/api/v1/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""
                {
                    "email": "john@example.com"
                }
                """))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.errors[0].field").value("name"));
    }
}

5. Integration Testing с @SpringBootTest

@SpringBootTest  // Загружает весь Application Context
@AutoConfigureMockMvc  // Включает MockMvc
public class UserControllerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private UserRepository userRepository;
    
    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
    }
    
    @Test
    void testCreateAndRetrieveUser() throws Exception {
        // 1. Создаём пользователя
        mockMvc.perform(post("/api/v1/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("""
                {
                    "name": "John",
                    "email": "john@example.com"
                }
                """))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").exists());
        
        // 2. Проверяем, что пользователь в БД
        MvcResult result = mockMvc.perform(get("/api/v1/users")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andReturn();
        
        String content = result.getResponse().getContentAsString();
        assertThat(content).contains("john@example.com");
    }
}

6. REST Assured — альтернатива MockMvc

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerRestAssuredTest {
    
    @LocalServerPort
    private int port;
    
    @BeforeEach
    void setUp() {
        RestAssured.port = port;
        RestAssured.basePath = "/api/v1";
    }
    
    @Test
    void testGetUser() {
        given()
            .header("Authorization", "Bearer token")
        .when()
            .get("/users/1")
        .then()
            .statusCode(200)
            .body("name", equalTo("John"))
            .body("email", equalTo("john@example.com"))
            .time(lessThan(1000L));
    }
    
    @Test
    void testCreateUser() {
        given()
            .contentType(ContentType.JSON)
            .body(new UserRequest("Jane", "jane@example.com"))
        .when()
            .post("/users")
        .then()
            .statusCode(201)
            .header("Location", notNullValue());
    }
}

7. Тестирование WebClient (для асинхронных запросов)

@WebMvcTest(AsyncUserController.class)
public class AsyncUserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testAsyncEndpoint() throws Exception {
        // Для async endpoints
        mockMvc.perform(get("/api/v1/users/1/async"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON));
    }
}

8. Пример комплексного теста

@WebMvcTest(PaymentController.class)
public class PaymentControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private PaymentService paymentService;
    
    @Test
    void testPaymentWorkflow() throws Exception {
        // 1. Создаём платёж
        MvcResult result = mockMvc.perform(
            post("/api/v1/payments")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {
                        "amount": 99.99,
                        "currency": "USD",
                        "method": "CREDIT_CARD"
                    }
                    """)
                .header("Authorization", "Bearer token"))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").exists())
            .andReturn();
        
        // 2. Извлекаем ID платежа
        String paymentId = JsonPath.read(result.getResponse().getContentAsString(), "$.id").toString();
        
        // 3. Получаем статус платежа
        mockMvc.perform(get("/api/v1/payments/" + paymentId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.status").value("PENDING"));
        
        // 4. Проверяем вызовы
        verify(paymentService, times(1)).createPayment(any());
    }
}

Лучшие практики

  1. Используй @WebMvcTest для тестирования только контроллеров
  2. Мокируй зависимости с @MockBean
  3. Тестируй HTTP статусы, заголовки, тело ответа
  4. Используй @SpringBootTest для интеграционных тестов
  5. Проверяй как позитивные, так и негативные сценарии
  6. Валидируй ошибки и исключения
  7. Используй описательные имена тестов
  8. Группируй тесты по функциональности