Pull to refresh

Spring MVC REST API: автономная конфигурация при написании модульных тестов

Reading time8 min
Views2K

Основные тезисы рассмотренные в посте:

  • Создание и настройка необходимых компонентов без дублирования кода

  • Отправка 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. Наши тестовые классы будут использовать этот метод при настройке тестируемой системы. После того, как мы добавили этот метод в наш объектный материнский класс, мы должны реализовать его, выполнив следующие шаги:

  1. Создайте новый MappingJackson2HttpMessageConverter объект. Этот объект может читать и записывать JSON с помощью Jackson.

  2. Настройте ObjectMapper который используется созданным MappingJackson2HttpMessageConverter объектом.

  3. Возвращает созданный объект.

После того, как мы внедрили этот метод, исходный код нашего материнского класса 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, выполнив следующие шаги:

  1. Создайте новый класс.

  2. Добавьте private MockMvc поле в созданный класс. Наш класс request builder будет использовать это поле при создании и отправке HTTP-запросов в тестируемую систему.

  3. Убедитесь, что мы можем внедрить используемый 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 поля:

  1. В requestBuilder поле содержится TodoItemRequestBuilder объект, который используется нашими методами тестирования, когда они отправляют HTTP-запросы в тестируемую систему.

  2. service Поле содержит TodoItemCrudService макет. Наши методы настройки будут использовать это поле, когда они заглушают методы с помощью Mockito. Кроме того, наши методы тестирования будут использовать это поле при проверке взаимодействий, которые произошли или не произошли между тестируемой системой и нашим макетом.

После того, как мы создали наш тестовый класс, его исходный код выглядит следующим образом:

class TodoItemCrudControllerTest {
 
    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;
}

Во-вторых, мы должны написать новый метод установки, который запускается перед запуском метода тестирования, и реализовать этот метод, выполнив следующие шаги:

  1. Создайте новый TodoItemCrudService макет и сохраните созданный макет в service поле.

  2. Создайте новый TodoItemCrudController объект (это тестируемый контроллер) и сохраните созданный объект в локальной переменной.

  3. Создайте новый MockMvc объект с помощью автономной конфигурации и сохраните созданный объект в локальной переменной. Не забудьте настроить пользовательский класс обработчика ошибок (он же @ControllerAdvice class) и пользовательский, HttpMessageConverter который может читать и записывать JSON с помощью Jackson (он же MappingJackson2HttpMessageConverter).

  4. Создайте новый 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).

Tags:
Hubs:
Total votes 5: ↑4 and ↓1+5
Comments1

Articles