Автор статьи: Рустем Галиев
IBM Senior DevOps Engineer & Integration Architect. Официальный DevOps ментор и коуч в IBM
Привет, Хабр! Сегодня мы посмотрим на то, как тестировать Spring Boot через MockMVC.
MockMvc – это тестовый фреймворк на стороне сервера, который позволяет проверять большинство функциональных возможностей приложения Spring MVC с помощью облегченных и целевых тестов
Прежде чем мы изучим механизм написания тестов с помощью MockMVC, нам нужно понять, почему вы хотите это сделать. Берем код простого книжного приложения. Давайте начнем с просмотра этого кода.
BookController.java package books; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/books") public class BookController { private final BookService bookService; public BookController(BookService bookService) { this.bookService = bookService; } @GetMapping public List<Book> findAll() { return bookService.findAll(); } @GetMapping("/{id}") public Book findOne(@PathVariable int id) { return bookService.findOne(id); } }
Класс помечен @RestController, что означает, что это класс, который будет принимать запросы и возвращать ответы:
Следующий код BookService.java
package books; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @Service public class BookService { private List<Book> books = new ArrayList<>(); public List<Book> findAll() { return books; } public Book findOne(int id) { return books.stream().filter(book -> book.getId() == id).findFirst().orElseThrow(BookNotFoundException::new); } /** * This method will be called once after the bean was initialized and add some seed data to the books list. */ @PostConstruct private void loadBooks() { Book one = new Book(1, "97 Things Every Java Programmer Should Know", "Kevlin Henney, Trisha Gee", "OReilly Media, Inc.", "May 2020", "9781491952696", "Java"); Book two = new Book(2, "Spring Boot: Up and Running", "Mark Heckler", "OReilly Media, Inc.", "February 2021", "9781492076919", "Spring"); Book three = new Book(3, "Hacking with Spring Boot 2.3: Reactive Edition", "Greg L. Turnquist", "Amazon.com Services LLC", "May 2020", "B086722L4L", "Spring"); books.addAll(Arrays.asList(one,two,three)); } }
Экземпляр класса BookService автоматически подключается Spring с помощью внедрения конструктора. Файл создает три книги и сохраняет их в списке. Метод findAll() вернет все книги в коллекции, а метод findOne вернет одну книгу или выдаст исключение, если она не найдена.
Прежде чем вносить какие-либо изменения, нам, вероятно, следует запустить приложение, чтобы убедиться, что все работает. Запустим приложение:
mvn spring-boot:run


Мы смогли запустить приложение и вручную протестировать каждую из конечных точек. Но что, если конечных точек больше двух? А если их пятьсот? Ручное тестирование каждого из них потребует много времени и чревато ошибками. Мы также хотим, чтобы ваши тесты выполнялись автоматически в процессе CI/CD, чтобы мы могли убедиться, что приложение работает должным образом, прежде чем развертывать его в другой среде.
Теперь, когда вы знаете, зачем вам нужно писать тесты, давайте посмотрим, как использовать MockMvc.
Создадим файл BookControllerTest.java
package books; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import java.util.List; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; // web-mvc-test class BookControllerTest { // mock-mvc // mock-bean // test-find-all // test-find-one private List<Book> getBooks() { Book one = new Book(1, "97 Things Every Java Programmer Should Know", "Kevlin Henney, Trisha Gee", "OReilly Media, Inc.", "May 2020", "9781491952696", "Java"); Book two = new Book(2, "Spring Boot: Up and Running", "Mark Heckler", "OReilly Media, Inc.", "February 2021", "9781492076919", "Spring"); return List.of(one, two); } }
Затем мы добавляем следующую аннотацию над объявлением класса:
@WebMvcTest(BookController.class)
Аннотацию WebMvcTest можно использовать для теста Spring MVC, ориентированного только на компоненты Spring MVC. Использование этой аннотации отключит полную автоконфигурацию и вместо этого применит только конфигурацию, относящуюся к тестам MVC (например, @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer и HandlerMethodArgumentResolver bean-компоненты, но не @Component, @Service или @Repository бобы).
В этом случае мы сообщаем Spring, что единственный bean-компонент, который следует использовать в этом тесте, — это класс BookController. Это может не иметь большого значения в нашем небольшом примере приложения, но по мере того, как наши приложения растут, и у вас есть сотни или тысячи bean-компонентов, управляемых Spring, мы не хотим, чтобы они все загружались в ApplicationContext только для запуска этого единственного теста.
По умолчанию тесты, аннотированные @WebMvcTest, также автоматически настраивают Spring Security и MockMVC. Это означает, что просто используя эту аннотацию, мы получим доступ к экземпляру MockMVC.
Инфраструктура Spring MVC Test, также известная как MockMVC, обеспечивает поддержку тестирования приложений Spring MVC. Он выполняет полную обработку запросов Spring MVC, но через фиктивные объекты запросов и ответов вместо работающего сервера.
На предыдущем шаге вы добавили аннотацию @WebMvcTest в класс BookControllerTest. При этом Spring автоматически настроит MockMVC для нас, и мы сможем получить экземпляр, добавив следующий код:
@Autowired MockMvc mvc;
Когда мы пишем юнит тесты, вы хотите сосредоточиться на одной функциональной единице. В этом случае вы хотите протестировать класс BookController. Если вы помните наш обзор приложения, конструктор BookController отвечает за подключение BookService. Мы можем использовать аннотацию Mockito @MockBean, чтобы добавить макеты в контекст приложения Spring. Затем в наших отдельных тестах мы можем имитировать поведение методов BookService:
@MockBean BookService bookService;
Теперь, когда у нас есть вся инфраструктура, мы можем приступить к написанию тестов нашего контроллера. Первое, что мы напишем, — это тестирование метода BookController.findAll(). Мы добавляем следующий код в ваш тестовый класс:
@Test void findAllShouldReturnAllBooks() throws Exception { Mockito.when(this.bookService.findAll()).thenReturn(getBooks()); mvc.perform(get("/books")) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()").value(2)); }
Метод Mockito.when() имитирует поведение метода findAll() BookService. Когда этот метод встречается в контроллере, он будет использовать ваше фиктивное поведение для возврата списка книг.
Метод execute исходит от MockMvc и выполняет запрос и возвращает тип, который позволяет связывать дальнейшие действия, такие как утверждение ожиданий, с результатом. Метод andExpect подтверждает ожидаемый результат. В этом примере мы проверяем успешный ответ, содержащий ожидаемое количество книг.
Если приложение уже запущено с предыдущего шага, вам нужно будет остановить его из командной строки. Затем запустите приложение с помощью следующей команды:
mvn spring-boot:run
Запустим тест
mvn -Dtest=BookControllerTest#findAllShouldReturnAllBooks test

Далее нам нужно протестировать функциональность получения одной книги. Мы будем использовать MockMVC для отправки запроса GET к /books/1, который является действительной книгой и должен вернуть первую книгу в коллекции. Оттуда мы проверяем значения в книге:
@Test void findOneShouldReturnValidBook() throws Exception { Mockito.when(this.bookService.findOne(1)).thenReturn(getBooks().get(0)); mvc.perform(get("/books/1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.title").value("97 Things Every Java Programmer Should Know")) .andExpect(jsonPath("$.author").value("Kevlin Henney, Trisha Gee")) .andExpect(jsonPath("$.publisher").value("OReilly Media, Inc.")) .andExpect(jsonPath("$.releaseDate").value("May 2020")) .andExpect(jsonPath("$.isbn").value("9781491952696")) .andExpect(jsonPath("$.topic").value("Java")); }
Запустим тест
mvn -Dtest=BookControllerTest#findOneShouldReturnValidBook test

Мы выполнили команду для запуска одного теста. Если вы хотите запустить все тесты, вы можете сделать это, выполнив следующую команду:
mvn test

Полный код:
package books; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import java.util.List; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(BookController.class) class BookControllerTest { @Autowired MockMvc mvc; @MockBean BookService bookService; @Test void findAllShouldReturnAllBooks() throws Exception { Mockito.when(this.bookService.findAll()).thenReturn(getBooks()); mvc.perform(get("/books")) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()").value(2)); } @Test void findOneShouldReturnValidBook() throws Exception { Mockito.when(this.bookService.findOne(1)).thenReturn(getBooks().get(0)); mvc.perform(get("/books/1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.title").value("97 Things Every Java Programmer Should Know")) .andExpect(jsonPath("$.author").value("Kevlin Henney, Trisha Gee")) .andExpect(jsonPath("$.publisher").value("OReilly Media, Inc.")) .andExpect(jsonPath("$.releaseDate").value("May 2020")) .andExpect(jsonPath("$.isbn").value("9781491952696")) .andExpect(jsonPath("$.topic").value("Java")); } private List<Book> getBooks() { Book one = new Book(1, "97 Things Every Java Programmer Should Know", "Kevlin Henney, Trisha Gee", "OReilly Media, Inc.", "May 2020", "9781491952696", "Java"); Book two = new Book(2, "Spring Boot: Up and Running", "Mark Heckler", "OReilly Media, Inc.", "February 2021", "9781492076919", "Spring"); return List.of(one, two); } }
В заключение приглашаем всех желающих на открытое занятие «Введение в облака, создание кластера в Mongo DB Atlas». На нем поговорим, какие бывают облака и настроим бесплатный Mongo DB кластер для своих проектов. Записаться на урок можно на странице курса «Разработчик на Spring Framework».
ссылка на оригинал статьи https://habr.com/ru/companies/otus/articles/746414/
Добавить комментарий