Тестирование Spring Boot через MockMVC

—

от автора

Автор статьи: Рустем Галиев

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *