Выбрать главу

Когда дело дойдет до тестирования реактивных контроллеров, Spring 5 не оставит нас в беде. Действительно, Spring 5 представил WebTestClient, новую утилиту для тестирования, которая упрощает написание тестов для реактивных контроллеров, написанных с использованием Spring WebFlux. Чтобы увидеть, как писать тесты с помощью WebTestClient, давайте используем его для тестирования метода recentTacos() из DesignTacoController, который вы написали в разделе 11.1.2.

11.3.1. Тестирование GET запросов

Одна вещь, которую мы хотели бы заявить о методе recentTacos(), заключается в том, что если для пути /design/recent выдается запрос HTTP GET, то ответ будет содержать полезную нагрузку JSON с не более чем 12 тако. Тестовый класс в следующем листинге - хорошее начало.

Листинг 11.1. Использование WebTestClient для тестирования DesignTacoController

package tacos;

import static org.mockito.Mockito.*;

import java.util.ArrayList;

import java.util.List;

import org.junit.Test;

import org.mockito.Mockito;

import org.springframework.http.MediaType;

import org.springframework.test.web.reactive.server.WebTestClient;

import reactor.core.publisher.Flux;

import tacos.Ingredient.Type;

import tacos.data.TacoRepository;

import tacos.web.api.DesignTacoController;

public class DesignTacoControllerTest {

   @Test

   public void shouldReturnRecentTacos() {

      Taco[] tacos = {            //Задание тестовых данных

         testTaco(1L), testTaco(2L),

         testTaco(3L), testTaco(4L),

         testTaco(5L), testTaco(6L),

         testTaco(7L), testTaco(8L),

         testTaco(9L), testTaco(10L),

         testTaco(11L), testTaco(12L),

         testTaco(13L), testTaco(14L),

         testTaco(15L), testTaco(16L)};

      Flux<Taco> tacoFlux = Flux.just(tacos);

      TacoRepository tacoRepo = Mockito.mock(TacoRepository.class);

      when(tacoRepo.findAll()).thenReturn(tacoFlux);    //Mocks TacoRepository

 

      WebTestClient testClient = WebTestClient.bindToController(

         new DesignTacoController(tacoRepo))

         .build();        //Создание WebTestClient

      

      testClient.get().uri("/design/recent")

         .exchange()        //Запрашивает последние тако

         .expectStatus().isOk() //Проверяет ожидаемый ответ

         .expectBody()

         .jsonPath("$").isArray()

         .jsonPath("$").isNotEmpty()

         .jsonPath("$[0].id").isEqualTo(tacos[0].getId().toString())

         .jsonPath("$[0].name").isEqualTo("Taco 1").jsonPath("$[1].id")

         .isEqualTo(tacos[1].getId().toString()).jsonPath("$[1].name")

         .isEqualTo("Taco 2").jsonPath("$[11].id")

         .isEqualTo(tacos[11].getId().toString())

         ...

         .jsonPath("$[11].name").isEqualTo("Taco 12").jsonPath("$[12]")

         .doesNotExist();

         .jsonPath("$[12]").doesNotExist();

   }

   ...

}

Первое, что должен сделать метод shouldReturnRecentTacos(), - это установить тестовые данные в форме Flux<Taco>. Этот Flux затем предоставляется как возвращаемое значение из метода findAll() фиктивного (mock) TacoRepository.

Что касается Taco объектов, которые будут опубликованы Flux, они создаются с помощью служебного метода testTaco(), который при присвоении номера создает объект Taco, ID и имя которого основаны на этом числе. Метод testTaco() реализован следующим образом:

private Taco testTaco(Long number) {

   Taco taco = new Taco();

   taco.setId(UUID.randomUUID());

   taco.setName("Taco " + number);

   List<IngredientUDT> ingredients = new ArrayList<>();

   ingredients.add(

      new IngredientUDT("INGA", "Ingredient A", Type.WRAP));

   ingredients.add(

      new IngredientUDT("INGB", "Ingredient B", Type.PROTEIN));

   taco.setIngredients(ingredients);

   return taco;

}

Для простоты все тестовые тако будут иметь одинаковые два ингредиента. Но их ID и имя будут определяться по указанному номеру.

Между тем, вернувшись в метод shouldReturnRecentTacos(), вы создали экземпляр DesignTacoController, внедрив в конструктор фиктивный TacoRepository. Контроллер передается в WebTestClient.bindToController() для создания экземпляра WebTestClient.

Завершив настройку, вы теперь готовы использовать WebTestClient для отправки запроса GET в /design/recent и проверки того, что ответ соответствует вашим ожиданиям. Вызов get().uri("/design/recent") описывает запрос, который вы хотите выполнить. Затем вызов метода exchange() отправляет запрос, который будет обработан контроллером, с которым связан WebTestClient - DesignTacoController.

Наконец, вы можете подтвердить, что ответ соответствует ожиданиям. Вызывая waitStatus(), вы утверждаете, что ответ имеет код состояния HTTP 200 (OK). После этого вы видите несколько вызовов jsonPath(), которые задают условия, что JSON в теле ответа имеет значения, которые он должен иметь. Последнее условие проверяет, что 12-й элемент (в массиве, начинающемся с нуля) не существует, поскольку в результате никогда не должно быть более 12 элементов.

Если JSON возвращается сложным, с большим количеством данных или сильно вложенными данными, это может быть утомительно использовать jsonPath(). Фактически, я пропустил многие вызовы jsonPath() в листинге 11.1, чтобы сэкономить место. В тех случаях, когда использование функции jsonPath() может быть неудобным, WebTestClient предлагает функцию json(), которая принимает параметр String, содержащий JSON, для сравнения ответа с ним.

Например, предположим, что вы создали полный ответ JSON в файле с именем recent-tacos.json и поместили его в путь к классам с путем /tacos. Затем вы можете переписать условия WebTestClient, чтобы они выглядели так: