1. Обзор
В наши дни мы рассчитываем вызывать REST API в большинстве наших сервисов. Spring предоставляет несколько вариантов для создания REST-клиента, рекомендуется использовать WebClient
.
В этом кратком руководстве мы узнаем, как проводить модульное тестирование сервисов, использующих WebClient
для вызова API .
2. Насмешка
У нас есть два основных варианта мока в наших тестах:
- Используйте Mockito , чтобы имитировать поведение
WebClient
- Используйте
WebClient
по-настоящему, но имитируйте службу, которую он вызывает, используяMockWebServer
(okhttp)
3. Использование Мокито
Mockito — самая распространенная библиотека для создания макетов для Java. Это хорошо для предоставления предопределенных ответов на вызовы методов, но все становится сложнее, когда они имитируют плавные API. Это связано с тем, что в свободном API между вызывающим кодом и макетом передается множество объектов.
Например, пусть класс EmployeeService с методом
getEmployeeById извлекает
данные через HTTP с помощью WebClient
:
public class EmployeeService {
public EmployeeService(String baseUrl) {
this.webClient = WebClient.create(baseUrl);
}
public Mono<Employee> getEmployeeById(Integer employeeId) {
return webClient
.get()
.uri("http://localhost:8080/employee/{id}", employeeId)
.retrieve()
.bodyToMono(Employee.class);
}
}
Мы можем использовать Mockito, чтобы издеваться над этим:
@ExtendWith(MockitoExtension.class)
public class EmployeeServiceTest {
@Test
void givenEmployeeId_whenGetEmployeeById_thenReturnEmployee() {
Integer employeeId = 100;
Employee mockEmployee = new Employee(100, "Adam", "Sandler",
32, Role.LEAD_ENGINEER);
when(webClientMock.get())
.thenReturn(requestHeadersUriSpecMock);
when(requestHeadersUriMock.uri("/employee/{id}", employeeId))
.thenReturn(requestHeadersSpecMock);
when(requestHeadersMock.retrieve())
.thenReturn(responseSpecMock);
when(responseMock.bodyToMono(Employee.class))
.thenReturn(Mono.just(mockEmployee));
Mono<Employee> employeeMono = employeeService.getEmployeeById(employeeId);
StepVerifier.create(employeeMono)
.expectNextMatches(employee -> employee.getRole()
.equals(Role.LEAD_ENGINEER))
.verifyComplete();
}
}
Как мы видим, нам нужно предоставить другой фиктивный объект для каждого вызова в цепочке с четырьмя различными требуемыми вызовами when
/ thenReturn
. Это многословно и громоздко . Это также требует от нас знания деталей реализации того, как именно наш сервис использует WebClient,
что делает этот способ тестирования ненадежным.
Итак, как мы можем написать лучшие тесты для WebClient?
4. Использование MockWebServer
MockWebServer
, созданный командой Square, представляет собой небольшой веб-сервер, который может получать HTTP-запросы и отвечать на них.
Взаимодействие с MockWebServer
из наших тестовых случаев позволяет нашему коду использовать настоящие HTTP-вызовы к локальной конечной точке . Мы получаем преимущество от тестирования предполагаемых HTTP-взаимодействий и избавляемся от проблем, связанных с имитацией сложного плавного клиента.
** Команда Spring ** рекомендует использовать MockWebServer
для написания интеграционных тестов .
``
4.1. Зависимости MockWebServer
Чтобы использовать MockWebServer
, нам нужно добавить зависимости Maven для okhttp и mockwebserver в наш pom.xml:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.0.1</version>
<scope>test</scope>
</dependency>
4.2. Добавление MockWebServer
в наш тест
Давайте протестируем наш EmployeeService
с помощью MockWebServer
:
public class EmployeeServiceMockWebServerTest {
public static MockWebServer mockBackEnd;
@BeforeAll
static void setUp() throws IOException {
mockBackEnd = new MockWebServer();
mockBackEnd.start();
}
@AfterAll
static void tearDown() throws IOException {
mockBackEnd.shutdown();
}
}
В приведенном выше классе JUnit Test методы setUp
и tearDown
заботятся о создании и завершении работы MockWebServer.
Следующим шагом является сопоставление порта фактического вызова службы REST с портом MockWebServer
:
@BeforeEach
void initialize() {
String baseUrl = String.format("http://localhost:%s",
mockBackEnd.getPort());
employeeService = new EmployeeService(baseUrl);
}
Теперь пришло время создать заглушку, чтобы MockWebServer
мог отвечать на HttpRequest
.
4.3. Заглушение ответа
Давайте воспользуемся удобным методом постановки в очередь MockWebServer , чтобы
поставить
в очередь тестовый ответ на веб-сервере:
@Test
void getEmployeeById() throws Exception {
Employee mockEmployee = new Employee(100, "Adam", "Sandler",
32, Role.LEAD_ENGINEER);
mockBackEnd.enqueue(new MockResponse()
.setBody(objectMapper.writeValueAsString(mockEmployee))
.addHeader("Content-Type", "application/json"));
Mono<Employee> employeeMono = employeeService.getEmployeeById(100);
StepVerifier.create(employeeMono)
.expectNextMatches(employee -> employee.getRole()
.equals(Role.LEAD_ENGINEER))
.verifyComplete();
}
Когда фактический вызов API выполняется из метода getEmployeeById(Integer employeeId)
в нашем классе EmployeeService ,
MockWebServer
ответит заглушкой из очереди .
4.4. Проверка запроса
Мы также можем убедиться, что MockWebServer
был отправлен правильный HttpRequest
.
В MockWebServer
есть удобный метод takeRequest
, который возвращает экземпляр RecordedRequest
:
RecordedRequest recordedRequest = mockBackEnd.takeRequest();
assertEquals("GET", recordedRequest.getMethod());
assertEquals("/employee/100", recordedRequest.getPath());
С помощью RecordedRequest
мы можем проверить полученный HttpRequest
, чтобы убедиться, что наш WebClient
отправил его правильно .
5. Вывод
В этой статье мы продемонстрировали два основных варианта имитации
клиентского кода REST на основе WebClient `` .
Хотя Mockito работает и может быть хорошим вариантом для простых примеров, рекомендуемый подход — использовать MockWebServer
.
Как всегда, исходный код этой статьи доступен на GitHub.