DEV Community

Cover image for 🧪 Тестирование с TestRestTemplate и MockMvc: миссия "Котики против багов" 🐞
Olga Lugacheva
Olga Lugacheva

Posted on

🧪 Тестирование с TestRestTemplate и MockMvc: миссия "Котики против багов" 🐞

Добро пожаловать, отважный охотник за багами! Сегодня мы будем использовать TestRestTemplate и MockMvc, чтобы исследовать дикие джунгли API. Ну что, вперед?

🚀 Подготовка к миссии

Как настоящий герой, ты должен быть готов к бою. Для начала, убедись, что:

  • Spring Boot в проекте.
  • Подключена зависимость для тестирования:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Enter fullscreen mode Exit fullscreen mode
  • У тебя есть класс с 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);
    }
}
Enter fullscreen mode Exit fullscreen mode
public class Cat {
    private Long id;
    private String name;

//геттеры, сеттеры, конструкторы
}
Enter fullscreen mode Exit fullscreen mode

Теперь ты готов исследовать!

ЧАСТЬ 1: TestRestTemplate

🧗 Поднимаемся в тестовые горы. Настройка TestRestTemplate

Убедись, что TestRestTemplate внедряется в тестовый класс:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CatControllerTest {

    @Autowired
    private TestRestTemplate testRestTemplate;
}
Enter fullscreen mode Exit fullscreen mode

1. POST — Добавляем нового котика

Пополнение запасов пушистиков:

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");
    }
Enter fullscreen mode Exit fullscreen mode

2. PUT — Апгрейд котика

Image description
У Барсика появился новый лук, например, галстук и шляпа, или он решил поменять имя?

    @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");
    }
Enter fullscreen mode Exit fullscreen mode

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"); // Проверяем имя второго
    }
Enter fullscreen mode Exit fullscreen mode
  1. Чтобы работать со списком мы используем ParameterizedTypeReference>, поскольку TestRestTemplate не могут напрямую десериализовать в List.
  2. Метод 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; // Мокаем сервис, если он есть
}

Enter fullscreen mode Exit fullscreen mode

Предположим, что в этом проекте мы перенесли логику по управлению котиками в 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")); // Проверяем имя
}

Enter fullscreen mode Exit fullscreen mode

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")); // Проверяем имя
}

Enter fullscreen mode Exit fullscreen mode

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")); // Имя второго
}

Enter fullscreen mode Exit fullscreen mode

💡 Лайфхаки для тестирования с MockMvc

  • JSONPath для детальной проверки. Используй jsonPath для проверки вложенных структур или массивов. Например:
.andExpect(jsonPath("$someArray[0].value").value("expectedValue"));
Enter fullscreen mode Exit fullscreen mode
  • Строгий контроль тела запроса/ответа С помощью content() можно не только отправить JSON, но и проверить возвращаемый JSON в исходном виде:
.andExpect(content().json("{\"id\":1,\"name\":\"Barsik\"}"));

Enter fullscreen mode Exit fullscreen mode
  • Проверка заголовков Если твой API возвращает специфичные заголовки, их тоже можно проверить:
.andExpect(header().string("Content-Type", "application/json"));

Enter fullscreen mode Exit fullscreen mode

happy cats

🎉 Эпилог

Теперь ты освоил два подхода к тестированию: TestRestTemplate и MockMvc. Используй MockMvc для тестов контроллеров, где важно проверить валидацию, сериализацию и интеграцию с сервисами. А для комплексных интеграционных тестов лучше подойдет TestRestTemplate.

И помни: котики (и твой код) всегда должны быть пушистыми и безупречными! 😸

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video