Коли ми розробляємо веб‑застосунок на 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.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ