Руководство по созданию фиктивных API с помощью WireMock

Архитектура микросервисов позволяет нам разрабатывать, тестировать и развертывать различные компоненты приложения независимо. Хотя такой компонент может быть разработан независимо, его тестирование в изоляции может быть сложным. Для настоящего интеграционного тестирования микросервиса мы должны протестировать его взаимодействие с другими API.

WireMock помогает в интеграционном тестировании, когда нам нужно имитировать внешние API для тестирования определенного API, зависящего от этих внешних API для завершения транзакции. WireMock — популярный HTTP-сервер-имитатор, который помогает имитировать API и заглушать ответы.

Стоит знать, что WireMock может работать как часть приложения или как отдельный процесс.

1. Зависимость Maven

Начните с импорта зависимостей wiremock в проект. Последнюю версию можно найти в репозитории Maven.

<dependency><groupId>com.github.tomakehurst</groupId><artifactId>wiremock-jre8</artifactId><version>2.33.2</version><scope>test</scope></dependency>

2. Самонастройка WireMock

Есть несколько способов начать работу с wiremock. Давайте рассмотрим их.

2.1. Использование WireMockServer

Самый простой способ создать экземпляр WireMockServer — вызвать его конструктор. По умолчанию wiremock использует имя хоста localhost и номер порта 8080. Мы можем инициализировать WireMockServer со случайным/фиксированным номером порта и пользовательским именем хоста с помощью метода configureFor().

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

Ниже приведен пример настройки wiremock с тестами JUnit 5. Обратите внимание, что этот метод можно использовать и в автономных приложениях Java. Он не ограничивается только тестами.

public class WireMockServerTest {static WireMockServer wireMockServer = new WireMockServer();@BeforeAllpublic static void beforeAll() {//WireMock.configureFor("custom-host", 9000, "/api-root-url");wireMockServer.start();}@AfterAllpublic static void afterAll() {wireMockServer.stop();}@AfterEachpublic void afterEach() {wireMockServer.resetAll();}}

2.2 Использование WireMockRule

WireMockRule был предпочтительным способом настройки, запуска и остановки сервера в тестах JUnit 4, хотя мы можем использовать его и в тестах JUnit 5. Он очень похож на класс WireMockServer по функциям и управлению.

Ниже приведен пример настройки Wiremock с тестами JUnit 4.

public class WireMockServerTest {@RuleWireMockRule wireMockRule = new WireMockRule();@Beforepublic void beforeAll() {wireMockRule.start();}@Afterpublic void afterAll() {wireMockRule.stop();}@AfterEachpublic void afterEach() {wireMockRule.resetAll();}}

2.3 Использование @WireMockTest

Аннотация @WireMockTest — еще один удобный способ усилить тесты JUnit с помощью wiremock. Это аннотация на уровне класса.

@WireMockTest запускает сервер Wiremock до начала тестов, останавливает сервер после окончания тестов и очищает контекст между тестами. Так что по сути он неявно выполняет все три шага, которые мы делали в предыдущих разделах, используя аннотации «до» и «после».

@WireMockTestpublic class WireMockTestAnnotationTest {//...}

2.4 Включение HTTPS

Мы можем включить HTTPS через параметр аннотации httpsEnabled. По умолчанию будет назначен случайный порт. Чтобы исправить номер порта HTTPS, используйте параметр httpsPort.

@WireMockTest(httpsEnabled = true, httpsPort = 8443)

С WireMockRule мы можем передать WireMockConfiguration.options() как аргумент конструктора. Те же шаги конфигурации работают и с WireMockServer.

WireMockServer wm= new WireMockServer(options().port(8080).httpsPort(8443));//or@Rulepublic WireMockRule wireMockRule= new WireMockRule(options().port(8080).httpsPort(8443

3. Простой пример WireMock

Начнем с создания очень простой заглушки API, вызовем ее с помощью любого HTTP-клиента и проверим, что фиктивный сервер был запущен.

  • Чтобы заглушить фиктивный ответ API, используйте метод WireMock.stubFor(). Он принимает экземпляр MappingBuilder, который мы можем использовать для построения информации о сопоставлении API, такой как URL, параметры и тело запроса, заголовки, авторизация и т. д.
  • Для тестирования API мы можем использовать любой HTTP-клиент, такой как HttpClient, RestTemplate или TestRestTemplate. В этой статье мы будем использовать TestRestTemplate.
  • Чтобы проверить, достиг ли запрос фиктивного API, мы можем использовать метод WireMock.verify().

Ниже приведен пример всех трех шагов с очень простым фиктивным API. Это должно помочь понять базовое использование wiremock.

Если мы предпочитаем использовать язык BDD в наших тестах, то мы можем заменить stubFor() на givenThat().

@WireMockTestpublic class WireMockTestAnnotationTest {@Testvoid simpleStubTesting(WireMockRuntimeInfo wmRuntimeInfo) {String responseBody = "Hello World !!";String apiUrl = "/api-url";//Define stubstubFor(get(apiUrl).willReturn(ok(responseBody)));//Hit API and check responseString apiResponse = getContent(wmRuntimeInfo.getHttpBaseUrl() + apiUrl);assertEquals(apiResponse, responseBody);//Verify API is hitverify(getRequestedFor(urlEqualTo(apiUrl)));}private String getContent(String url) {TestRestTemplate testRestTemplate = new TestRestTemplate();return testRestTemplate.getForObject(url, String.class);}}

4. Предварительное использование

4.1 Настройка API-запроса

Wiremock предоставляет множество полезных статических методов для заглушек частей запросов и ответов API.

Используйте get(), put(), post(), delete() и другие методы для сопоставления соответствующих методов HTTP. Используйте any() для сопоставления любого метода HTTP, соответствующего URL.

stubFor(delete("/url").willReturn(ok()));stubFor(post("/url").willReturn(ok()));stubFor(any("/url").willReturn(ok()));

Используйте другие методы, такие как withHeader(), withCookie(), withQueryParam(), withRequestBody() и т. д., чтобы задать другие части запроса. Мы также можем передать информацию об авторизации, используя информацию withBasicAuth().

stubFor(get(urlPathEqualTo("/api-url")).withHeader("Accept", containing("xml")).withCookie("JSESSIONID", matching(".*")).withQueryParam("param-name", equalTo("param-value")).withBasicAuth("username", "plain-password")//.withRequestBody(equalToXml("part-of-request-body")).withRequestBody(matchingXPath("//root-tag"))/*.withMultipartRequestBody(aMultipart().withName("preview-image").withHeader("Content-Type", containing("image")).withBody(equalToJson("{}")))*/.willReturn(aResponse()));

4.2 Настройка ответа API

Обычно нас интересуют только статус ответа, заголовки ответа и тело ответа. WireMock поддерживает заглушку всех этих компонентов в ответе с помощью простых методов.

stubFor(get(urlEqualTo("/api-url")).willReturn(aResponse().withStatus(200).withStatusMessage("Everything was just fine!").withHeader("Content-Type", "application/json").withBody("{ \"message\": \"Hello world!\" }")));

4.3 Тестирование задержки API и тайм-аутов

Чтобы протестировать отложенный ответ API и то, как текущий API обрабатывает тайм-ауты, мы можем использовать следующие методы:

С помощью withFixedDelay() можно настроить фиксированную задержку, при которой ответ не будет возвращен до истечения указанного количества миллисекунд.

stubFor(get(urlEqualTo("/api-url")).willReturn(ok().withFixedDelay(2000)));

WithRandomDelay() можно использовать для получения задержки из случайного распределения. WireMock поддерживает два типа случайных распределений: равномерное распределение и логнормальное распределение.

stubFor(get(urlEqualTo("/api-url")).willReturn(aResponse().withStatus(200).withFixedDelay(2000)//.withLogNormalRandomDelay(90, 0.1)//.withRandomDelay(new UniformDistribution(15, 25))));

Мы также можем использовать withChunkedDribbleDelay() для имитации медленной сети, где ответ принимается порциями с задержками между ними. Он принимает два параметра: numberOfChunks и totalDuration.

stubFor(get("/api-url").willReturn(aResponse().withStatus(200).withBody("api-response").withChunkedDribbleDelay(5, 1000)));

4.4 Тестирование плохих ответов

В архитектуре микросервисов API может вести себя ненормально в любое время, поэтому потребители API должны быть готовы обрабатывать такие случаи. Wiremock помогает в этом виде обработки ответов, заглушая ошибочные ответы с помощью метода withFault().

stubFor(get(urlEqualTo("/api-url")).willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));

Поддерживаются следующие константы перечисления:

  • EMPTY_RESPONSE: Вернуть полностью пустой ответ.
  • RANDOM_DATA_THEN_CLOSE: Отправить мусор, а затем закрыть соединение.
  • MALFORMED_RESPONSE_CHUNK: Отправить заголовок статуса OK, затем мусор, затем закрыть соединение.
  • CONNECTION_RESET_BY_PEER: Закрытие соединения, вызывающее ошибку «Сброс соединения одноранговым узлом».

5. Проверка обращений к API

Если мы хотим проверить, были ли соблюдены фиктивные API и сколько раз, мы можем сделать это с помощью метода WireMock.verify() следующим образом.

verify(exactly(1), postRequestedFor(urlEqualTo(api_url)).withHeader("Content-Type", "application/JSON"));

Существует довольно много методов проверки количества попаданий, например lessThan(), lessThanOrExactly(), exact(), moreThanOrExactly() и moreThan().

verify(lessThan(5), anyRequestedFor(anyUrl()));verify(lessThanOrExactly(5), anyRequestedFor(anyUrl()));verify(exactly(5), anyRequestedFor(anyUrl()));verify(moreThanOrExactly(5), anyRequestedFor(anyUrl()));verify(moreThan(5), anyRequestedFor(anyUrl()));

6. Заключение

Это руководство по WireMock поможет вам начать интеграционное тестирование, имитируя внешние REST API. Оно охватывает различные методы инициализации WireMockServer и запуска, остановки или сброса при необходимости.

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

Sourceocde на Github

Прокрутить вверх