Когда дело дойдет до тестирования реактивных контроллеров, 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, чтобы они выглядели так: