🧪 Тестирование с TestRestTemplate и MockMvc: миссия "Котики против багов" 🐞
Olga Lugacheva
Posted on November 26, 2024
Добро пожаловать, отважный охотник за багами! Сегодня мы будем использовать TestRestTemplate и MockMvc, чтобы исследовать дикие джунгли API. Ну что, вперед?
🚀 Подготовка к миссии
Как настоящий герой, ты должен быть готов к бою. Для начала, убедись, что:
- Spring Boot в проекте.
- Подключена зависимость для тестирования:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- У тебя есть класс с REST-контроллером. Пример — "Управление котиками" 🐈:
@RestController
@RequestMapping("/cats")
public class CatController {
private final Map<Long, Cat> cats = new HashMap<>();
@GetMapping
public List<Cat> getAllCats() {
return new ArrayList<>(cats.values());
}
@GetMapping("/{id}")
public Cat getCat(@PathVariable Long id) {
if (cats.containsKey(id)) {
return cats.get(id);
} else {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cat not found");
}
}
@PostMapping
public Cat addCat(@RequestBody Cat cat) {
cat.setId(System.nanoTime()); // Генерация уникального ID
cats.put(cat.getId(), cat);
return cat;
}
@PutMapping("/{id}")
public Cat updateCat(@PathVariable Long id, @RequestBody Cat updatedCat) {
if (cats.containsKey(id)) {
updatedCat.setId(id);
cats.put(id, updatedCat);
return updatedCat;
} else {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Cat not found");
}
}
@DeleteMapping("/{id}")
public void deleteCat(@PathVariable Long id) {
cats.remove(id);
}
}
public class Cat {
private Long id;
private String name;
//геттеры, сеттеры, конструкторы
}
Теперь ты готов исследовать!
ЧАСТЬ 1: TestRestTemplate
🧗 Поднимаемся в тестовые горы. Настройка TestRestTemplate
Убедись, что TestRestTemplate внедряется в тестовый класс:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CatControllerTest {
@Autowired
private TestRestTemplate testRestTemplate;
}
1. POST — Добавляем нового котика
Пополнение запасов пушистиков:
@Test
void shouldAddCat() {
Cat newCat = new Cat("Barsik");
ResponseEntity<Cat> response = testRestTemplate.postForEntity("/cats", newCat, Cat.class);
// Проверка статуса
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// Проверка тела ответа
Cat createdCat = response.getBody();
assertThat(createdCat).isNotNull();
assertThat(createdCat.getId()).isNotNull();
assertThat(createdCat.getName()).isEqualTo("Barsik");
}
2. PUT — Апгрейд котика
У Барсика появился новый лук, например, галстук и шляпа, или он решил поменять имя?
@Test
void shouldUpdateCat() {
// Сначала добавляем котика
Cat newCat = new Cat("Barsik");
Cat createdCat = testRestTemplate.postForObject("/cats", newCat, Cat.class);
// Обновляем имя
createdCat.setName("Murzik");
ResponseEntity<Cat> response = testRestTemplate.exchange(
"/cats/" + createdCat.getId(),
HttpMethod.PUT,
new HttpEntity<>(createdCat),
Cat.class
);
// Проверяем статус
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// Проверяем обновленный объект
Cat updatedCat = response.getBody();
assertThat(updatedCat).isNotNull();
assertThat(updatedCat.getId()).isEqualTo(createdCat.getId());
assertThat(updatedCat.getName()).isEqualTo("Murzik");
}
3. GET — Получаем список всех котиков
Мы должны убедиться, что наши пушистики на месте:
@Test
void shouldGetAllCats() {
testRestTemplate.postForObject("/cats", new Cat("Barsik"), Cat.class);
testRestTemplate.postForObject("/cats", new Cat("Murzik"), Cat.class);
// Выполняем GET-запрос
ResponseEntity<List<Cat>> response = testRestTemplate.exchange(
"/cats",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Cat>>() {
}
);
// Проверяем статус и содержимое
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().size()).isEqualTo(2); // Проверяем количество элементов
assertThat(response.getBody().get(0).getName()).isEqualTo("Barsik"); // Проверяем имя первого
assertThat(response.getBody().get(1).getName()).isEqualTo("Murzik"); // Проверяем имя второго
}
- Чтобы работать со списком мы используем ParameterizedTypeReference>, поскольку TestRestTemplate не могут напрямую десериализовать в List.
- Метод exchange используется вместо getForEntity для большей гибкости при работе с ParameterizedTypeReference
ЧАСТЬ 2: MockMvc
Теперь переходим на новый уровень и осваиваем MockMvc. Это мощный инструмент для тестирования контроллеров, который позволяет тестировать REST API без поднятия реального веб-сервера.
🚀 Что такое MockMvc и зачем оно нужно?
MockMvc — это способ эмулировать запросы и ответы на уровне Spring MVC.
Он быстро проверяет контроллеры, не запуская сервер.
Это особенно полезно, если хочешь протестировать валидацию, обработку запросов, сериализацию/десериализацию и взаимодействие с сервисами.
Подготовка тестового класса
Для MockMvc нам понадобится настроить контекст:
@WebMvcTest(CatController.class) // Тестируем только контроллер
public class CatControllerMockMvcTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CatService catService; // Мокаем сервис, если он есть
}
Предположим, что в этом проекте мы перенесли логику по управлению котиками в CatService
1. POST — Добавление котика
Для начала отправим POST-запрос и проверим, что котик успешно добавлен:
@Test
void shouldAddCat() {
// Создаем тестового котика
Cat savedCat = new Cat(1L, "Barsik");
// Эмулируем работу сервиса
when(catService.addCat(any(Cat.class))).thenReturn(savedCat);
// Отправляем POST-запрос
mockMvc.perform(post("/cats")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(newCat)))
.andExpect(status().isOk()) // Проверяем статус 200 OK
.andExpect(jsonPath("$.name").value("Barsik")); // Проверяем имя
}
2. PUT — Обновление котика
Теперь обновим котика, чтобы он выглядел еще лучше:
@Test
void shouldUpdateCat() throws Exception {
Cat updatedCat = new Cat(1L, "Murzik");
// Эмулируем работу сервиса
when(catService.updateCat(eq(catId), any(Cat.class))).thenReturn(updatedCat);
// Отправляем PUT-запрос
mockMvc.perform(put("/cats/{id}", catId)
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(updatedCat)))
.andExpect(status().isOk()) // Проверяем статус 200 OK
.andExpect(jsonPath("$.id").value(catId)) // Проверяем, что ID не изменился
.andExpect(jsonPath("$.name").value("Murzik")); // Проверяем имя
}
3. GET — Получение списка котиков
@Test
void shouldGetAllCats() throws Exception {
List<Cat> cats = List.of(
new Cat(1L, "Barsik"),
new Cat(2L, "Murzik")
);
// Эмулируем работу сервиса
when(catService.getAllCats()).thenReturn(cats);
// Отправляем GET-запрос
mockMvc.perform(get("/cats"))
.andExpect(status().isOk()) // Проверяем статус 200 OK
.andExpect(jsonPath("$.length()").value(2)) // Проверяем размер списка
.andExpect(jsonPath("$[0].id").value(1)) // Проверяем первый объект
.andExpect(jsonPath("$[0].name").value("Barsik")) // Имя первого
.andExpect(jsonPath("$[1].id").value(2)) // Проверяем второй объект
.andExpect(jsonPath("$[1].name").value("Murzik")); // Имя второго
}
💡 Лайфхаки для тестирования с MockMvc
- JSONPath для детальной проверки. Используй jsonPath для проверки вложенных структур или массивов. Например:
.andExpect(jsonPath("$someArray[0].value").value("expectedValue"));
- Строгий контроль тела запроса/ответа С помощью content() можно не только отправить JSON, но и проверить возвращаемый JSON в исходном виде:
.andExpect(content().json("{\"id\":1,\"name\":\"Barsik\"}"));
- Проверка заголовков Если твой API возвращает специфичные заголовки, их тоже можно проверить:
.andExpect(header().string("Content-Type", "application/json"));
🎉 Эпилог
Теперь ты освоил два подхода к тестированию: TestRestTemplate и MockMvc. Используй MockMvc для тестов контроллеров, где важно проверить валидацию, сериализацию и интеграцию с сервисами. А для комплексных интеграционных тестов лучше подойдет TestRestTemplate.
И помни: котики (и твой код) всегда должны быть пушистыми и безупречными! 😸
Posted on November 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 26, 2024