Когда мы разрабатываем веб-приложение на Spring — нам нужно проверять, правильно ли оно работает. Для этого существует замечательный инструмент MockMvc. Представьте, что ваше приложение — это современное кафе. В обычной ситуации, чтобы протестировать работу, нужно запустить всё заведение: кухню, зал, посадить официантов. Это как запуск полноценного сервера — долго и сложно. MockMvc — это как если бы вы создали миниатюрную копию кафе с одним поваром и окошком выдачи. Через это окошко можно делать заказы (отправлять запросы) и получать готовые блюда (ответы), не запуская всё заведение целиком. В мире Spring это означает, что мы можем тестировать приложение в облегченном режиме — без полноценного сервера и подключения к базе данных.
Подготовка окружения для тестов
Для начала убедимся, что мы подключили все необходимые зависимости для тестирования. В вашем pom.xml (если вы используете Maven) или build.gradle (если Gradle) должны быть указаны зависимости для Spring Boot Starter Test:
Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Gradle:
testImplementation 'org.springframework.boot:spring-boot-starter-test'
MockMvc уже встроен в Spring Boot Starter Test, так что дополнительных зависимостей не требуется.
Простое тестирование REST API с MockMvc
Входные данные
Допустим, у нас есть REST-контроллер для управления сущностями "Продукт":
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
// Это упрощённая заглушка. В реальной жизни здесь будет вызов сервиса.
if (id == 1L) {
return ResponseEntity.ok(new Product(1L, "Кофе", 5.0));
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
И у нас есть сущность Product:
public class Product {
private Long id;
private String name;
private Double price;
// Конструкторы, геттеры и сеттеры.
public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
// Геттеры и сеттеры
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
Пишем тесты
Теперь переходим к написанию тестов для ProductController. Мы хотим убедиться, что:
- Если мы запрашиваем ресурс с существующим ID, то получаем корректный результат.
- Если ресурс с указанным ID не существует, возвращается статус 404.
public class Product {
private Long id;
private String name;
private Double price;
// Конструкторы, геттеры и сеттеры.
public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
// Геттеры и сеттеры
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
- Аннотация
@WebMvcTest: указывает, что мы будем тестировать только слой контроллеров. Spring автоматически поднимет минимальное необходимое окружение. - Метод
mockMvc.perform(): эмулирует HTTP-запрос. Здесь мы указываем путь (/api/products/1) и метод (GET). - Методы
andExpect(): помогают проверить, что приложение возвращает ожидаемый ответ. Мы можем проверять:- Статус ответа (
isOk(),isNotFound()и другие). - Тип контента (
contentType()). - Содержимое JSON-ответа (
jsonPath()).
- Статус ответа (
Тестирование POST-запросов
Теперь добавим в наш контроллер метод для создания продукта:
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
product.setId(2L); // Заглушка: всегда возвращаем один и тот же ID
return ResponseEntity.status(HttpStatus.CREATED).body(product);
}
Напишем тест для этого метода. Мы хотим убедиться, что:
- POST-запрос с корректными данными создаёт продукт и возвращает статус 201 (Created).
- В теле ответа возвращается созданный продукт.
@Test
public void testCreateProduct() throws Exception {
String newProductJson = """
{
"name": "Чай",
"price": 3.5
}
""";
mockMvc.perform(post("/api/products") // Эмулируем POST-запрос
.contentType(MediaType.APPLICATION_JSON) // Указываем, что тело запроса в формате JSON
.content(newProductJson)) // Передаём JSON-данные
.andExpect(status().isCreated()) // Ожидаем статус 201 Created
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) // Ответ тоже JSON
.andExpect(jsonPath("$.id").value(2)) // Проверяем ID
.andExpect(jsonPath("$.name").value("Чай")) // Проверяем имя продукта
.andExpect(jsonPath("$.price").value(3.5)); // Проверяем цену продукта
}
Частые ошибки и особенности
- Ошибка "No qualifying bean of type '...' found"
При использовании@WebMvcTestв тестовом окружении поднимаются только контроллеры. Если в вашем контроллере есть зависимости от сервисов или других компонентов, не забудьте замокировать их с помощью Mockito:@MockBean private ProductService productService; - Некорректное использование
@Autowired
Всегда используйте правильные аннотации (@Autowiredили@MockBean) для зависимости. - JSON-формат в запросах
Убедитесь, что отправляемые JSON-данные в тестах корректны. ИспользуйтеcontentType(MediaType.APPLICATION_JSON). - Проблемы с
jsonPath
Рекомендуется использовать онлайн-валидаторы JSONPath, чтобы убедиться, что ваш путь указан корректно.
На этом этапе мы научились тестировать REST API с использованием MockMvc. Эти знания помогут вам не только выявлять баги на ранних этапах разработки, но и уверенно проходить собеседования, где вас могут попросить продемонстрировать работу с MockMvc.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ