Logo

dev-resources.site

for different kinds of informations.

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

Published at
11/26/2024
Categories
webdev
java
spring
testing
Author
easycat
Categories
4 categories in total
webdev
open
java
open
spring
open
testing
open
Author
7 person written this
easycat
open
🧪 Тестирование с 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.

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

spring Article's
30 articles in total
Favicon
Launched a Web version of my Project using Java Spring Framework, Spring Boot Web
Favicon
Developing a project using Java Spring Framework, JSON, JPA and PostgreSQL
Favicon
Волшебные скоупы: Как Spring организует работу бинов
Favicon
Quando usar ResponseEntity?
Favicon
What is load balancing and how to do it on client side
Favicon
Capturing and Testing Logs in Java with SLF4J and Logback: A Simple Guide
Favicon
Can you find the Output of this Java Code
Favicon
Ways to Speed Up Spring Boot Application Startup Time
Favicon
Apache wicket with spring boot example application: notice board
Favicon
Handling the "Forwarded" Header in Spring Boot Applications
Favicon
About UriComponentsBuilder and UriComponents
Favicon
Spring Boot: About @SpringBootApplication
Favicon
Spring Security: CSRF protection
Favicon
ISBN Stacks — A look at a possible Spring Application implementation without annotations
Favicon
spring profiles dev production
Favicon
Learn Spring Data JPA, Part - 1
Favicon
Methods for Efficient Large File Processing in Spring Boot
Favicon
Hexagonal Architecture — A Favorite Lyrics Spring Boot — Java Example
Favicon
Validation in Spring REST Framework (SRF)
Favicon
spring
Favicon
Spring Security: Redirect to login page if access pages which is for authorized user only
Favicon
Lambda vs. Named Functions: Choosing the Right Tool for the Job
Favicon
Techniques for Mastering Spring Interceptors: Detailed Guide with Examples
Favicon
Techniques for Using @NoRepositoryBean in Spring Data JPA: A Comprehensive Guide
Favicon
Understanding the Spring Framework: A Developer’s Journey to Clean Code 🚀
Favicon
@PreConstruct and @PostConstruct annotation Spring Boot Example
Favicon
Java’s Functional Programming: the OOP influence
Favicon
🧪 Тестирование с TestRestTemplate и MockMvc: миссия "Котики против багов" 🐞
Favicon
spring boot
Favicon
Reasons to Use WireMock for Testing REST APIs in Spring Boot Applications

Featured ones: