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

На чем пишешь тесты на контроллеры

1.7 Middle🔥 231 комментариев
#Spring Boot и Spring Data#Тестирование

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

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

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

Тестирование контроллеров в Spring Boot

Для тестирования контроллеров я использую Spring Boot Test + MockMvc, комбинируя разные подходы в зависимости от уровня тестирования и требований проекта.

Основной инструмент: MockMvc + @WebMvcTest

MockMvc — это встроенная Spring утилита для тестирования HTTP endpoints без запуска реального сервера:

import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testGetUserById_Success() throws Exception {
        // Arrange
        Long userId = 1L;
        UserDTO userDTO = new UserDTO(1L, "John Doe", "john@example.com");
        given(userService.getUserById(userId)).willReturn(userDTO);
        
        // Act & Assert
        mockMvc.perform(get("/api/v1/users/{id}", userId)
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1))
            .andExpect(jsonPath("$.name").value("John Doe"))
            .andExpect(jsonPath("$.email").value("john@example.com"));
        
        // Verify
        verify(userService, times(1)).getUserById(userId);
    }
    
    @Test
    void testGetUserById_NotFound() throws Exception {
        // Arrange
        Long userId = 999L;
        given(userService.getUserById(userId))
            .willThrow(new UserNotFoundException("User not found"));
        
        // Act & Assert
        mockMvc.perform(get("/api/v1/users/{id}", userId))
            .andExpect(status().isNotFound());
    }
}

Тестирование POST с телом запроса

@WebMvcTest(UserController.class)
class UserControllerPostTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testCreateUser_Success() throws Exception {
        // Arrange
        CreateUserRequest request = new CreateUserRequest(
            "Jane Doe", 
            "jane@example.com"
        );
        UserDTO createdUser = new UserDTO(2L, "Jane Doe", "jane@example.com");
        
        given(userService.createUser(any(CreateUserRequest.class)))
            .willReturn(createdUser);
        
        // Act & Assert
        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(2))
            .andExpect(jsonPath("$.name").value("Jane Doe"));
    }
    
    @Test
    void testCreateUser_ValidationFailed() throws Exception {
        // Arrange — неправильный email
        CreateUserRequest request = new CreateUserRequest(
            "Jane Doe", 
            "invalid-email"
        );
        
        // Act & Assert
        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isBadRequest());
    }
}

Интеграционные тесты: @SpringBootTest

Для более полного тестирования (с БД и всеми слоями):

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class UserControllerIntegrationTest {
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private UserRepository userRepository;
    
    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
    }
    
    @Test
    void testGetUser_WithRealDatabase() {
        // Arrange — создаём реального пользователя в БД
        User user = new User(null, "John Doe", "john@example.com");
        User saved = userRepository.save(user);
        
        // Act
        ResponseEntity<UserDTO> response = restTemplate.getForEntity(
            "/api/v1/users/" + saved.getId(),
            UserDTO.class
        );
        
        // Assert
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getName()).isEqualTo("John Doe");
    }
    
    @Test
    void testCreateUser_SavesToDatabase() {
        // Arrange
        CreateUserRequest request = new CreateUserRequest(
            "Jane Doe", 
            "jane@example.com"
        );
        
        // Act
        ResponseEntity<UserDTO> response = restTemplate.postForEntity(
            "/api/v1/users",
            request,
            UserDTO.class
        );
        
        // Assert
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(userRepository.count()).isEqualTo(1);
    }
}

Сравнение подходов

ПодходУровеньСкоростьКогда использовать
MockMvc @WebMvcTestUnit⚡⚡⚡ БыстроТестирование только контроллера, мокируем service
MockMvc @SpringBootTestIntegration⚡⚡ СреднеПолный контекст Spring, но без реальной БД
TestRestTemplateIntegration⚡ МедленноРеальный сервер в памяти, реальная БД
RestAssuredE2E🐢 Очень медленноЗапущенный сервер, все компоненты вместе

Тестирование с аутентификацией

@WebMvcTest(UserController.class)
class AuthenticatedUserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testGetUserProfile_WithAuthentication() throws Exception {
        // Arrange
        UserDTO userProfile = new UserDTO(1L, "John Doe", "john@example.com");
        given(userService.getUserById(any())).willReturn(userProfile);
        
        // Act & Assert — используем SecurityMockMvcRequestPostProcessors
        mockMvc.perform(get("/api/v1/users/me")
                .with(jwt()
                    .jwt(jwt -> jwt.subject("user-1"))
                    .authorities(new SimpleGrantedAuthority("ROLE_USER"))))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John Doe"));
    }
    
    @Test
    void testGetUserProfile_Unauthorized() throws Exception {
        // Act & Assert — без аутентификации
        mockMvc.perform(get("/api/v1/users/me"))
            .andExpect(status().isUnauthorized());
    }
}

Тестирование обработки ошибок

@WebMvcTest(UserController.class)
class UserControllerErrorHandlingTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testGetUser_ServiceThrowsException() throws Exception {
        // Arrange
        given(userService.getUserById(1L))
            .willThrow(new UserNotFoundException("User not found"));
        
        // Act & Assert
        mockMvc.perform(get("/api/v1/users/1"))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.message").value("User not found"))
            .andExpect(jsonPath("$.timestamp").exists());
    }
}

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

@WebMvcTest(UserController.class)
class UserControllerParametersTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testGetUsers_WithPagination() throws Exception {
        // Arrange
        List<UserDTO> users = List.of(
            new UserDTO(1L, "User 1", "user1@example.com"),
            new UserDTO(2L, "User 2", "user2@example.com")
        );
        given(userService.getUsers(0, 10)).willReturn(users);
        
        // Act & Assert
        mockMvc.perform(get("/api/v1/users")
                .param("page", "0")
                .param("size", "10"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.length()").value(2));
    }
    
    @Test
    void testCreateUser_WithCustomHeaders() throws Exception {
        // Act & Assert
        mockMvc.perform(post("/api/v1/users")
                .header("X-Request-ID", "12345")
                .header("X-Client-Version", "1.0")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"Test\",\"email\":\"test@example.com\"}"))
            .andExpect(status().isCreated());
    }
}

Проверка response headers

@WebMvcTest(UserController.class)
class UserControllerHeadersTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testCreateUser_ReturnsLocationHeader() throws Exception {
        // Arrange
        UserDTO created = new UserDTO(1L, "John", "john@example.com");
        given(userService.createUser(any())).willReturn(created);
        
        // Act & Assert
        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"John\",\"email\":\"john@example.com\"}"))
            .andExpect(status().isCreated())
            .andExpect(header().exists("Location"))
            .andExpect(header().string("Location", containsString("/api/v1/users/1")));
    }
}

Best Practices для тестов контроллеров

// ✅ ХОРОШО
@WebMvcTest(UserController.class)  // Тестируем только контроллер
class UserControllerTest {
    @Autowired private MockMvc mockMvc;
    @MockBean private UserService userService;  // Мокируем зависимости
    
    @Test
    void testEndpoint_SuccessCase() throws Exception {
        // Given/Arrange
        given(userService.getUser(1L)).willReturn(expected);
        
        // When/Act
        mockMvc.perform(get("/api/v1/users/1"))
        
        // Then/Assert
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1));
    }
    
    @Test
    void testEndpoint_ErrorCase() throws Exception {
        // Тестируем также ошибки
        given(userService.getUser(999L))
            .willThrow(new NotFoundException());
        
        mockMvc.perform(get("/api/v1/users/999"))
            .andExpect(status().isNotFound());
    }
}

// ❌ ПЛОХО
@SpringBootTest  // Запускаем весь контекст
class BadControllerTest {
    @Autowired private UserService userService;  // Не мокируем
    
    // Тесты медленные и хрупкие
}

Структура tестового класса

@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired private MockMvc mockMvc;
    @Autowired private ObjectMapper objectMapper;
    @MockBean private UserService userService;
    @MockBean private UserValidator userValidator;
    
    // Группируем тесты по операциям
    
    @Nested
    class GetUserTests {
        @Test void testGetUser_Success() { ... }
        @Test void testGetUser_NotFound() { ... }
        @Test void testGetUser_Unauthorized() { ... }
    }
    
    @Nested
    class CreateUserTests {
        @Test void testCreateUser_Success() { ... }
        @Test void testCreateUser_ValidationFailed() { ... }
        @Test void testCreateUser_Conflict() { ... }
    }
    
    @Nested
    class DeleteUserTests {
        @Test void testDeleteUser_Success() { ... }
        @Test void testDeleteUser_NotFound() { ... }
    }
}

Заключение

Для тестирования контроллеров я использую:

  1. MockMvc @WebMvcTest (80% тестов) — быстро, тестируем логику контроллера
  2. MockMvc @SpringBootTest (15% тестов) — интеграция с БД и другими компонентами
  3. TestRestTemplate (5% тестов) — E2E тесты с реальным сервером

MockMvc — это мощный инструмент, позволяющий писать быстрые и надёжные тесты контроллеров. Всегда мокируйте зависимости в unit тестах, тестируйте как happy path, так и error cases, и группируйте тесты для лучшей читаемости.

На чем пишешь тесты на контроллеры | PrepBro