Архитектура микросервисов позволяет нам разрабатывать, тестировать и развертывать различные компоненты приложения независимо. Хотя такой компонент может быть разработан независимо, его тестирование в изоляции может быть сложным. Для настоящего интеграционного тестирования микросервиса мы должны протестировать его взаимодействие с другими 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.