Основные тезисы рассмотренные в посте:
Создание и настройка необходимых компонентов без дублирования кода
Отправка HTTP-запросов в тестируемую систему без дублирования кода
Настройка Spring MVC Test framework, при написании модульных тестов для Spring MVC REST API с помощью JUnit 5.
Тестируемая система
Тестируемая система состоит из двух классов:
Класс
TodoItemCrudControllerсодержит методы контроллера, которые реализуют REST API, обеспечивающий операции CRUD для элементов todo.TodoItemCrudServiceКласс предоставляет операции CRUD для элементов todo.TodoItemCrudControllerКласс вызывает свои методы при обработке HTTP-запросов.
Соответствующая часть TodoItemCrudController класса выглядит следующим образом:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/todo-item")
public class TodoItemCrudController {
private final TodoItemCrudService service;
@Autowired
public TodoItemCrudController(TodoItemCrudService service) {
this.service = service;
}
}Далее мы создадим компоненты, расширяющие минимальную конфигурацию Spring MVC, которая создается при настройке тестируемой системы, с использованием автономной конфигурации.
Создание необходимых компонентов
Мы должны минимизировать количество пользовательских компонентов, которые мы включаем в тестируемую систему. Однако может быть сложно определить необходимые компоненты, если у нас нет большого опыта. Вот почему я написал два правила, которые помогают нам выбирать необходимые компоненты.:
Мы должны создать и настроить пользовательский,
HttpMessageConverterесли мы хотим использовать пользовательский,ObjectMapperкоторый преобразует JSON в объекты и наоборот.Если
Localeобъект вводится в метод тестируемого контроллера в качестве параметра метода, мы должны создать и настроить пользовательскийLocaleResolver.
Важно!
Если мы используем пользовательский
ObjectMapper, мы должны написать фабричный метод, который возвращает сконфигурированныйObjectMapperобъект. Это помогает нам писать тесты, которые отправляют документы JSON в тестируемую систему. Кроме того, это гарантирует, что наши методы тестирования и платформа тестирования Spring MVC используют одну и ту же конфигурацию.Мы не настраиваем пользовательский,
LocaleResolverпотому что наш контроллер его не использует. Однако, если он вам нужен, вам следует использоватьFixedLocaleResolverкласс.
Мы можем создать и настроить необходимые компоненты, выполнив сле��ующие действия:
Сначала мы должны создать public материнский класс объекта, который содержит заводские методы, которые создают и настраивают необходимые компоненты. После того, как мы создали наш объектный материнский класс, мы должны убедиться, что никто не сможет создать его экземпляр.
После того, как мы создали наш объектный материнский класс, его исходный код выглядит следующим образом:
public final class WebTestConfig {
private WebTestConfig() {}
}Важно!
Поскольку мы не хотим помещать наши тестовые классы в тот же пакет, что и наш объектный материнский класс, наш объектный материнский класс должен быть
public. Кроме того, это означает, что все фабричные методы, добавленные в наш объектный материнский класс, также должны бытьpublic.Мы должны использовать материнский класс object, потому что несколько тестовых классов используют одни и те же компоненты, и мы не хотим добавлять дублирующий код в наш набор тестов.
Во-вторых, мы должны добавить public и static вызываемый метод objectMapper() в наш материнский класс object. Этот метод создает и настраивает новый ObjectMapper объект и возвращает созданный объект. Наши тесты будут использовать этот метод при создании документов JSON, которые отправляются в тестируемую систему.
После того, как мы написали этот метод, исходный код нашего материнского класса object выглядит следующим образом:
import com.fasterxml.jackson.databind.ObjectMapper;
public final class WebTestConfig {
private WebTestConfig() {}
public static ObjectMapper objectMapper() {
return new ObjectMapper();
}
}В-третьих, мы должны добавить вызываемый public метод static and objectMapperHttpMessageConverter() в наш материнский класс object. Наши тестовые классы будут использовать этот метод при настройке тестируемой системы. После того, как мы добавили этот метод в наш объектный материнский класс, мы должны реализовать его, выполнив следующие шаги:
Создайте новый
MappingJackson2HttpMessageConverterобъект. Этот объект может читать и записывать JSON с помощью Jackson.Настройте
ObjectMapperкоторый используется созданнымMappingJackson2HttpMessageConverterобъектом.Возвращает созданный объект.
После того, как мы внедрили этот метод, исходный код нашего материнского класса object выглядит следующим образом:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
public final class WebTestConfig {
private WebTestConfig() {}
public static MappingJackson2HttpMessageConverter objectMapperHttpMessageConverter() {
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper());
return converter;
}
public static ObjectMapper objectMapper() {
return new ObjectMapper();
}
}Теперь мы можем создавать необходимые компоненты, используя материнский класс object. Давайте двигаться дальше и выясним, как мы можем создать класс конструктора запросов, который отправляет HTTP-запросы в тестируемую систему.
Создание класса Request Builder
Когда мы пишем модульные тесты для реального веб-приложения или REST API, мы замечаем, что каждый метод тестирования создает новый HTTP-запрос и отправляет его тестируемой системе. Это плохая ситуация, потому что дублирующий код затрудняет написание и поддержку наших тестов.
Мы можем решить эту проблему, используя классы конструктора запросов. Класс конструктора запросов - это класс, который выполняет эти условия:
Он содержит методы, которые создают и отправляют HTTP-запросы в тестируемую систему с помощью
MockMvcобъекта.Каждый метод должен возвращать
ResultActionsобъект, который позволяет нам писать утверждения для возвращаемого HTTP-ответа.
Мы можем написать наш класс request builder, выполнив следующие шаги:
Создайте новый класс.
Добавьте
private MockMvcполе в созданный класс. Наш класс request builder будет использовать это поле при создании и отправке HTTP-запросов в тестируемую систему.Убедитесь, что мы можем внедрить используемый
MockMvcобъект вmockMvcполе, используя внедрение конструктора.
После того, как мы создали наш класс request builder, его исходный код выглядит следующим образом:
import org.springframework.test.web.servlet.MockMvc;
class TodoItemRequestBuilder {
private final MockMvc mockMvc;
TodoItemRequestBuilder(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
}Этот класс конструктора запросов бесполезен, потому что мы не писали методы, которые создают и отправляют HTTP-запросы в тестируемую систему. Мы подробнее поговорим о классах конструктора запросов, когда научимся писать модульные тесты для Spring MVC REST API.
Далее мы научимся настраивать тестируемую систему.
Настройка тестируемой системы
Мы можем создать новый тестовый класс и настр��ить тестируемую систему, выполнив следующие шаги:
Сначала нам нужно создать новый тестовый класс и добавить необходимые поля в наш тестовый класс. В нашем тестовом классе есть два private поля:
В
requestBuilderполе содержитсяTodoItemRequestBuilderобъект, который используется нашими методами тестирования, когда они отправляют HTTP-запросы в тестируемую систему.serviceПоле содержитTodoItemCrudServiceмакет. Наши методы настройки будут использовать это поле, когда они заглушают методы с помощью Mockito. Кроме того, наши методы тестирования будут использовать это поле при проверке взаимодействий, которые произошли или не произошли между тестируемой системой и нашим макетом.
После того, как мы создали наш тестовый класс, его исходный код выглядит следующим образом:
class TodoItemCrudControllerTest {
private TodoItemRequestBuilder requestBuilder;
private TodoItemCrudService service;
}Во-вторых, мы должны написать новый метод установки, который запускается перед запуском метода тестирования, и реализовать этот метод, выполнив следующие шаги:
Создайте новый
TodoItemCrudServiceмакет и сохраните созданный макет вserviceполе.Создайте новый
TodoItemCrudControllerобъект (это тестируемый контроллер) и сохраните созданный объект в локальной переменной.Создайте новый
MockMvcобъект с помощью автономной конфигурации и сохраните созданный объект в локальной переменной. Не забудьте настроить пользовательский класс обработчика ошибок (он же@ControllerAdviceclass) и пользовательский,HttpMessageConverterкоторый может читать и записывать JSON с помощью Jackson (он жеMappingJackson2HttpMessageConverter).Создайте новый
TodoItemRequestBuilderобъект и сохраните созданный объект вrequestBuilderполе.
После того, как мы написали наш метод установки, исходный код нашего тестового класса выглядит следующим образом:
import org.junit.jupiter.api.BeforeEach;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.*;
import static org.mockito.Mockito.mock;
class TodoItemCrudControllerTest {
private TodoItemRequestBuilder requestBuilder;
private TodoItemCrudService service;
@BeforeEach
void configureSystemUnderTest() {
service = mock(TodoItemCrudService.class);
TodoItemCrudController testedController = new TodoItemCrudController(service);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testedController)
.setControllerAdvice(new TodoItemErrorHandler())
.setMessageConverters(objectMapperHttpMessageConverter())
.build();
requestBuilder = new TodoItemRequestBuilder(mockMvc);
}
}Если тестируемая система выдает пользовательские исключения, и мы хотим убедиться, что эти исключения обрабатываются должным образом, мы должны настроить используемый
@ControllerAdviceкласс (он же класс обработчика ошибок).
Теперь мы можем настроить тестируемую систему, используя автономную конфигурацию. Давайте обобщим то, что мы узнали из этого поста в блоге.
Итоги
Этот пост в блоге научил нас:
Созданию необходимых пользовательских компонентов без дублирования кода, с помощью
publicматеринского класс object, который имеет методыpublicиstaticfactory.Отправке HTTP-запросы в тестируемую систему без дублирования кода, используя класс request builder.
Если мы хотим настроить тестируемую систему с помощью автономной конфигурации, мы должны вызвать
standaloneSetup()методMockMvcBuildersкласса.Мы можем включать пользовательские компоненты в тестируемую систему, используя методы класса
StandaloneMockMvcBuilder.Наиболее распространенными пользовательскими компонентами, которые включены в тестируемую систему, являются: пользовательский
@ControllerAdviceкласс и пользовательскийHttpMessageConverter, который может читать и записывать JSON с помощью Jackson (он жеMappingJackson2HttpMessageConverter).
