← Назад к вопросам
На чем пишешь тесты на контроллеры
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 @WebMvcTest | Unit | ⚡⚡⚡ Быстро | Тестирование только контроллера, мокируем service |
| MockMvc @SpringBootTest | Integration | ⚡⚡ Средне | Полный контекст Spring, но без реальной БД |
| TestRestTemplate | Integration | ⚡ Медленно | Реальный сервер в памяти, реальная БД |
| RestAssured | E2E | 🐢 Очень медленно | Запущенный сервер, все компоненты вместе |
Тестирование с аутентификацией
@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() { ... }
}
}
Заключение
Для тестирования контроллеров я использую:
- MockMvc @WebMvcTest (80% тестов) — быстро, тестируем логику контроллера
- MockMvc @SpringBootTest (15% тестов) — интеграция с БД и другими компонентами
- TestRestTemplate (5% тестов) — E2E тесты с реальным сервером
MockMvc — это мощный инструмент, позволяющий писать быстрые и надёжные тесты контроллеров. Всегда мокируйте зависимости в unit тестах, тестируйте как happy path, так и error cases, и группируйте тесты для лучшей читаемости.