Основные тезисы рассмотренные в посте:
Создание и настройка необходимых компонентов без дублирования кода
Отправка 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
объект с помощью автономной конфигурации и сохраните созданный объект в локальной переменной. Не забудьте настроить пользовательский класс обработчика ошибок (он же@ControllerAdvice
class) и пользовательский,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
иstatic
factory.Отправке HTTP-запросы в тестируемую систему без дублирования кода, используя класс request builder.
Если мы хотим настроить тестируемую систему с помощью автономной конфигурации, мы должны вызвать
standaloneSetup()
методMockMvcBuilders
класса.Мы можем включать пользовательские компоненты в тестируемую систему, используя методы класса
StandaloneMockMvcBuilder
.Наиболее распространенными пользовательскими компонентами, которые включены в тестируемую систему, являются: пользовательский
@ControllerAdvice
класс и пользовательскийHttpMessageConverter
, который может читать и записывать JSON с помощью Jackson (он жеMappingJackson2HttpMessageConverter
).