Рубрики
Без рубрики

Интеграционное тестирование весной

Краткое руководство по написанию интеграционных тестов для веб-приложения Spring.

Автор оригинала: baeldung.

1. Обзор

Интеграционное тестирование играет важную роль в цикле разработки приложений, проверяя сквозное поведение системы.

В этой статье мы рассмотрим, как мы можем использовать тестовую платформу Spring MVC для написания и запуска интеграционных тестов, которые тестируют контроллеры без явного запуска контейнера сервлетов.

2. Подготовка

Для выполнения интеграционных тестов, которые мы будем использовать в этой статье, требуется несколько зависимостей Maven. Прежде всего, нам понадобится новейший junit-jupiter-engine , junit-jupiter-api и Spring test зависимости:


    org.junit.jupiter
    junit-jupiter-engine
    5.7.0
    test


    org.junit.jupiter
    junit-jupiter-api
    5.7.0
    test


    org.springframework
    spring-test
    5.3.3
    test

Для эффективного утверждения результатов мы также будем использовать Hamcrest и JSON path :


    org.hamcrest
    hamcrest-library
    2.2
    test


    com.jayway.jsonpath
    json-path
    2.5.0
    test

3. Конфигурация теста Spring MVC

Теперь давайте представим, как настроить и запустить тесты с поддержкой Spring.

3.1. Включить Spring в тестах с JUnit 5

JUnit 5 определяет интерфейс расширения, с помощью которого классы могут интегрироваться с тестом JUnit.

Мы можем включить это расширение , добавив аннотацию @ExtendWith в наши тестовые классы и указав класс расширения для загрузки . Для запуска весеннего теста мы используем SpringExtension.class.

Нам также нужна аннотация @ContextConfiguration для загрузки конфигурации контекста и начальной загрузки контекста, который будет использовать наш тест .

Давайте посмотрим:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ApplicationConfig.class })
@WebAppConfiguration
public class GreetControllerIntegrationTest {
    ....
}

Обратите внимание, как в @ContextConfiguration мы предоставляем ApplicationConfig.class класс config, который загружает конфигурацию, необходимую для данного конкретного теста.

Здесь мы используем класс конфигурации Java для указания конфигурации контекста. Аналогично, мы можем использовать конфигурацию на основе XML:

@ContextConfiguration(locations={""})

Наконец, мы также аннотируем тест с помощью @ WebAppConfiguration , который загрузит контекст веб-приложения .

По умолчанию он ищет корневое веб-приложение по пути src/main/webapp. Мы можем переопределить это местоположение, просто передав атрибут value :

@WebAppConfiguration(value = "")

3.2. Объект WebApplicationContext

WebApplicationContext предоставляет конфигурацию веб-приложения. Он загружает все компоненты приложения и контроллеры в контекст.

Теперь мы сможем подключить webapplicationcontext прямо в тест:

@Autowired
private WebApplicationContext webApplicationContext;

3.3. Насмешливые компоненты веб-контекста

MockMvc обеспечивает поддержку тестирования Spring MVC. Он инкапсулирует все компоненты веб-приложений и делает их доступными для тестирования.

Давайте посмотрим, как его использовать:

private MockMvc mockMvc;
@BeforeEach
public void setup() throws Exception {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
}

Мы инициализируем объект MockMvc в аннотированном методе @beforeEach , чтобы нам не приходилось инициализировать его внутри каждого теста.

3.4. Проверка Тестовой конфигурации

Давайте убедимся, что мы правильно загружаем объект WebApplicationContext ( WebApplicationContext ). Мы также проверим, что прикрепляется правильный ServletContext :

@Test
public void givenWac_whenServletContext_thenItProvidesGreetController() {
    ServletContext servletContext = webApplicationContext.getServletContext();
    
    Assert.assertNotNull(servletContext);
    Assert.assertTrue(servletContext instanceof MockServletContext);
    Assert.assertNotNull(webApplicationContext.getBean("greetController"));
}

Обратите внимание, что мы также проверяем, что a GreetController.java bean существует в веб – контексте. Это гарантирует правильную загрузку пружинных бобов. На этом этапе выполняется настройка интеграционного теста. Теперь давайте посмотрим, как мы можем протестировать методы ресурсов с помощью объекта MockMvc .

4. Написание Интеграционных Тестов

В этом разделе мы рассмотрим основные операции, доступные в тестовой среде.

Мы покажем, как отправлять запросы с переменными пути и параметрами. Кроме того, мы приведем несколько примеров, которые покажут, как утверждать, что правильное имя представления разрешено или что тело ответа соответствует ожидаемому.

Фрагменты, показанные ниже, используют статический импорт из классов M mockmvcrequestbuilders или MockMvcResultMatchers .

4.1. Проверьте Имя представления

Мы можем вызвать /домашнюю страницу конечную точку из нашего теста как :

http://localhost:8080/spring-mvc-test/

или

http://localhost:8080/spring-mvc-test/homePage

Во-первых, давайте посмотрим тестовый код:

@Test
public void givenHomePageURI_whenMockMVC_thenReturnsIndexJSPViewName() {
    this.mockMvc.perform(get("/homePage")).andDo(print())
      .andExpect(view().name("index"));
}

Давайте разберемся с этим:

  • метод perform() вызовет метод запроса GET, который возвращает действия Result . Используя этот результат, мы можем иметь ожидания утверждения относительно ответа, например, его содержимого, состояния HTTP или заголовка
  • и Do(print()) напечатает запрос и ответ. Это полезно для получения подробного представления в случае ошибки
  • и Expect() будет ожидать предоставленный аргумент. В нашем случае мы ожидаем, что “индекс” будет возвращен через MockMvcResultMatchers.view()

4.2. Проверьте Тело Ответа

Мы вызовем конечную точку /greet из нашего теста как:

http://localhost:8080/spring-mvc-test/greet

Ожидаемый результат будет:

{
    "id": 1,
    "message": "Hello World!!!"
}

Давайте посмотрим тестовый код:

@Test
public void givenGreetURI_whenMockMVC_thenVerifyResponse() {
    MvcResult mvcResult = this.mockMvc.perform(get("/greet"))
      .andDo(print()).andExpect(status().isOk())
      .andExpect(jsonPath("$.message").value("Hello World!!!"))
      .andReturn();
    
    Assert.assertEquals("application/json;charset=UTF-8", 
      mvcResult.getResponse().getContentType());
}

Давайте посмотрим, что именно происходит:

  • andExpect(MockMvcResultMatchers.status().isOk()) проверит, что состояние HTTP ответа Ok ( 200) . Это гарантирует, что запрос был успешно выполнен
  • andExpect(MockMvcResultMatchers.JSONPath(“$.message”).value(“Hello World!!!”)) проверит, что содержимое ответа совпадает с аргументом ” и Return() вернет объект Mvc Result , который используется, когда нам нужно проверить что-то, что непосредственно не достижимо библиотекой. В этом случае мы добавили
  • assertEquals , чтобы соответствовать типу содержимого ответа, извлеченного из объекта MvcResult

4.3. Отправить Запрос GET С Переменной Path

4.3. Отправить Запрос GET С Переменной Path

http://localhost:8080/spring-mvc-test/greetWithPathVariable/John

4.3. Отправить Запрос GET С Переменной Path

{
    "id": 1,
    "message": "Hello World John!!!"
}

4.3. Отправить Запрос GET С Переменной Path

@Test
public void givenGreetURIWithPathVariable_whenMockMVC_thenResponseOK() {
    this.mockMvc
      .perform(get("/greetWithPathVariable/{name}", "John"))
      .andDo(print()).andExpect(status().isOk())
      
      .andExpect(content().contentType("application/json;charset=UTF-8"))
      .andExpect(jsonPath("$.message").value("Hello World John!!!"));
}

4.3. Отправить Запрос GET С Переменной Path

4.3. Отправить Запрос GET С Переменной Path

4.4. Отправить Запрос GET С Параметрами Запроса

Мы вызовем /приветствие с переменной запроса?name={name} конечная точка из нашего теста как:

http://localhost:8080/spring-mvc-test/greetWithQueryVariable?name=John%20Doe

В этом случае ожидаемый результат будет:

{
    "id": 1,
    "message": "Hello World John Doe!!!"
}

Теперь давайте посмотрим тестовый код:

@Test
public void givenGreetURIWithQueryParameter_whenMockMVC_thenResponseOK() {
    this.mockMvc.perform(get("/greetWithQueryVariable")
      .param("name", "John Doe")).andDo(print()).andExpect(status().isOk())
      .andExpect(content().contentType("application/json;charset=UTF-8"))
      .andExpect(jsonPath("$.message").value("Hello World John Doe!!!"));
}

Теперь давайте посмотрим тестовый код:

Теперь давайте посмотрим тестовый код:

this.mockMvc.perform(
  get("/greetWithQueryVariable?name={name}", "John Doe"));

Теперь давайте посмотрим тестовый код:

Теперь давайте посмотрим тестовый код:

http://localhost:8080/spring-mvc-test/greetWithPost

Мы должны получить в качестве результата:

{
    "id": 1,
    "message": "Hello World!!!"
}

И наш тестовый код:

@Test
public void givenGreetURIWithPost_whenMockMVC_thenVerifyResponse() {
    this.mockMvc.perform(post("/greetWithPost")).andDo(print())
      .andExpect(status().isOk()).andExpect(content()
      .contentType("application/json;charset=UTF-8"))
      .andExpect(jsonPath("$.message").value("Hello World!!!"));
}

MockMvcRequestBuilders.post(“/приветствие с сообщением”) отправит запрос на сообщение . Мы можем задать переменные пути и параметры запроса таким же образом, как и раньше, в то время как данные формы могут быть заданы только с помощью метода param () , аналогичного запросу

http://localhost:8080/spring-mvc-test/greetWithPostAndFormData

MockMvcRequestBuilders.post(“/приветствие с сообщением”) || отправит запрос на сообщение || . Мы можем задать переменные пути и параметры запроса таким же образом, как и раньше, в то время как данные формы могут быть заданы только с помощью метода || param ()||, аналогичного запросу

id=1;name=John%20Doe

MockMvcRequestBuilders.post(“/приветствие с сообщением”) || отправит запрос на сообщение || . Мы можем задать переменные пути и параметры запроса таким же образом, как и раньше, в то время как данные формы могут быть заданы только с помощью метода || param ()||, аналогичного запросу

{
    "id": 1,
    "message": "Hello World John Doe!!!"
}

MockMvcRequestBuilders.post(“/приветствие с сообщением”) || отправит запрос на сообщение || . Мы можем задать переменные пути и параметры запроса таким же образом, как и раньше, в то время как данные формы могут быть заданы только с помощью метода || param ()||, аналогичного запросу

@Test
public void givenGreetURI_whenMockMVC_thenVerifyResponse() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/greet"))
      .andDo(print())
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Hello World!!!"))
      .andReturn();
 
   assertEquals("application/json;charset=UTF-8", mvcResult.getResponse().getContentType());
}

MockMvcRequestBuilders.post(“/приветствие с сообщением”) отправит запрос на сообщение . Мы можем задать переменные пути и параметры запроса таким же образом, как и раньше, в то время как данные формы могут быть заданы только с помощью метода param () , аналогичного запросу

5. Ограничения MockMvc

MockMvc предоставляет элегантный и простой в использовании API для вызова веб-конечных точек, а также для проверки и утверждения их ответа одновременно. Несмотря на все свои преимущества, он имеет несколько ограничений.

Прежде всего, он использует подкласс DispatcherServlet для обработки тестовых запросов. Чтобы быть более конкретным, TestDispatcherServlet отвечает за вызов контроллеров и выполнение всех

Класс MockMvc | обертывает этот TestDispatcherServlet внутренне. Таким образом, каждый раз, когда мы отправляем запрос с помощью метода perform () , MockMvc будет напрямую использовать базовый TestDispatcherServlet|/. Таким образом, реальных сетевых подключений нет, и, следовательно, мы не будем тестировать весь сетевой стек при использовании MockMvc .

Кроме того, поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring .

Кроме того, поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring .

Кроме того, поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring .

Кроме того, || поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring || .

@SpringBootTest(webEnvironment = DEFINED_PORT)
public class GreetControllerRealIntegrationTest {

    @Before
    public void setUp() {
        RestAssured.port = DEFAULT_PORT;
    }

    @Test
    public void givenGreetURI_whenSendingReq_thenVerifyResponse() {
        given().get("/greet")
          .then()
          .statusCode(200);
    }
}

Кроме того, поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring .

Кроме того, || поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring || .

Кроме того, || поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring || .

Кроме того, || поскольку Spring готовит поддельный контекст веб-приложения для подделки HTTP-запросов и ответов, он может не поддерживать все функции полномасштабного приложения Spring || .

Мы также рассмотрели создание объектов WebApplicationContext и MockMvc , которые сыграли важную роль в вызове конечных точек приложения.

В дальнейшем мы рассмотрели, как мы можем отправлять запросы GET и POST с различными вариантами передачи параметров и как проверить статус ответа HTTP, заголовок и содержимое.

В качестве заключительного замечания мы также оценили некоторые ограничения MockMvc. Знание этих ограничений может помочь нам принять обоснованное решение о том, как мы собираемся реализовать наши тесты.

Наконец, доступна реализация всех этих примеров и фрагментов кода