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)