Как стать автором
Обновить
1388.97
OTUS
Цифровые навыки от ведущих экспертов

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

Время на прочтение7 мин
Количество просмотров20K
Автор статьи: Рустем Галиев

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».

Теги:
Хабы:
Всего голосов 16: ↑8 и ↓8+3
Комментарии9

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS