WireMock для Java: Полное руководство по HTTP-мок-серверу
1. Что это и зачем нужна
WireMock — это мощная библиотека для Java (и других языков через standalone-сервер), которая позволяет создавать HTTP-мок-серверы для тестирования. По сути, это эмулятор реального HTTP-сервиса (API, REST, SOAP), который вы можете настроить так, чтобы он возвращал заранее определённые ответы на определённые запросы. Это незаменимый инструмент в арсенале разработчика и тестировщика, работающего с микросервисной архитектурой или любыми системами, которые зависят от внешних HTTP-сервисов.
Зачем это нужно? В современной разработке приложения редко существуют изолированно. Они общаются с базами данных, платёжными шлюзами, сервисами авторизации, погодными API и десятками других внешних систем. Тестирование таких интеграций "вживую" — это головная боль: внешний сервис может быть недоступен, медленным, возвращать неожиданные данные или, что ещё хуже, брать с вас деньги за каждый запрос. WireMock решает эту проблему, позволяя:
- Тестировать в изоляции: Вы тестируете только свой код, не завися от доступности и стабильности внешних сервисов.
- Симулировать редкие сценарии: Легко заставить мок возвращать ошибки 500, таймауты, пустые ответы или специфические заголовки — то, что сложно воспроизвести с реальным сервисом.
- Ускорять тесты: Мок-сервер отвечает за миллисекунды, в то время как реальный сервис может отвечать секунды.
- Параллелизовать разработку: Пока backend-команда ещё не реализовала API, frontend-команда уже может писать и тестировать свой код, используя WireMock.
WireMock работает как полноценный HTTP-сервер (на базе Jetty), который запускается внутри вашего теста или как отдельный процесс. Он перехватывает HTTP-запросы, сравнивает их с настроенными правилами (stubs) и возвращает соответствующий ответ. Библиотека поддерживает запись и воспроизведение (record/playback), проверку состояния (verify), работу с шаблонами (Handlebars), симуляцию задержек и многое другое.
2. Установка
WireMock для Java распространяется через Maven Central. Добавьте следующую зависимость в ваш pom.xml (для Maven) или build.gradle (для Gradle).
Maven (pom.xml):
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.9.1</version> <!-- Проверьте актуальную версию на Maven Central -->
<scope>test</scope>
</dependency>
Gradle (build.gradle):
testImplementation 'org.wiremock:wiremock:3.9.1'
Важно: Начиная с версии 3.x, WireMock требует Java 11 или выше. Если вы используете Java 8, вам нужно будет использовать версию 2.x (например, 2.35.1), но настоятельно рекомендуется перейти на Java 11+.
3. Быстрый старт — минимальный рабочий пример
Давайте создадим простейший тест, который запускает WireMock, настраивает заглушку для GET-запроса к /api/hello и проверяет, что клиент получает ожидаемый ответ.
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class QuickStartTest {
@Test
public void testSimpleStub() throws Exception {
// 1. Создаём и запускаем WireMock-сервер на порту 8089
WireMockServer wireMockServer = new WireMockServer(8089);
wireMockServer.start();
// 2. Настраиваем заглушку (stub)
// Если придёт GET-запрос на /api/hello, вернуть статус 200 и тело "Hello, World!"
configureFor("localhost", 8089);
stubFor(get(urlEqualTo("/api/hello"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/plain")
.withBody("Hello, World!")));
// 3. Создаём HTTP-клиент и отправляем запрос
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8089/api/hello"))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 4. Проверяем результат
assertEquals(200, response.statusCode());
assertEquals("Hello, World!", response.body());
// 5. Останавливаем сервер
wireMockServer.stop();
}
}
Этот пример демонстрирует основную идею: Stub (правило) -> Request (запрос) -> Response (ответ).
4. Основные методы и функции
Рассмотрим ключевые методы WireMock, которые покрывают 90% задач. Все примеры предполагают, что сервер запущен и конфигурация выполнена через configureFor().
4.1. stubFor(MappingBuilder mappingBuilder)
Сигнатура: public static void stubFor(MappingBuilder mappingBuilder)
Что делает: Регистрирует новое правило (stub) в WireMock. Это основной метод для настройки поведения мок-сервера.
stubFor(get(urlEqualTo("/users"))
.willReturn(aResponse()
.withStatus(200)
.withBody("[{\"id\":1, \"name\":\"Alice\"}]")));
4.2. get(String url) / post(String url) / put(String url) / delete(String url)
Сигнатура: public static MappingBuilder get(String url) (и аналогично для других HTTP-методов)
Что делает: Создают строитель (builder) для матчинга запроса по HTTP-методу и URL. Возвращают MappingBuilder, который можно дополнительно настроить (заголовки, тело запроса и т.д.).
// POST запрос
stubFor(post(urlEqualTo("/api/items"))
.withRequestBody(matchingJsonPath("$.name"))
.willReturn(aResponse()
.withStatus(201)
.withHeader("Location", "/api/items/123")));
4.3. urlEqualTo(String url) / urlPathEqualTo(String urlPath) / urlPathMatching(String regex)
Сигнатура: public static UrlPattern urlEqualTo(String url)
Что делает: Определяют стратегию сопоставления URL. urlEqualTo требует точного совпадения (включая query-параметры). urlPathEqualTo игнорирует query-параметры. urlPathMatching использует регулярное выражение.
// Точное совпадение: /api/users?page=1
stubFor(get(urlEqualTo("/api/users?page=1"))
.willReturn(aResponse().withStatus(200)));
// Совпадение пути: /api/users/42 (query-параметры игнорируются)
stubFor(get(urlPathEqualTo("/api/users/42"))
.willReturn(aResponse().withStatus(200)));
// Регулярное выражение: /api/users/123, /api/users/abc
stubFor(get(urlPathMatching("/api/users/[a-z0-9]+"))
.willReturn(aResponse().withStatus(200)));
4.4. aResponse()
Сигнатура: public static ResponseDefinitionBuilder aResponse()
Что делает: Создаёт строитель для определения ответа. Позволяет настроить статус, заголовки, тело, задержку и т.д.
stubFor(get(urlEqualTo("/error"))
.willReturn(aResponse()
.withStatus(500)
.withHeader("Content-Type", "application/json")
.withBody("{\"error\":\"Internal Server Error\"}")
.withFixedDelay(2000))); // Задержка 2 секунды
4.5. withRequestBody(WireMock.request matching)
Сигнатура: public MappingBuilder withRequestBody(ContentPattern<?> bodyPattern)
Что делает: Добавляет условие на тело запроса. Поддерживает точное совпадение, JSON Path, XPath, регулярные выражения и т.д.
// Точное совпадение тела
stubFor(post(urlEqualTo("/api/login"))
.withRequestBody(equalToJson("{\"username\":\"admin\", \"password\":\"secret\"}"))
.willReturn(aResponse().withStatus(200)));
// Совпадение по JSON Path
stubFor(post(urlEqualTo("/api/order"))
.withRequestBody(matchingJsonPath("$.items[0].productId"))
.willReturn(aResponse().withStatus(201)));
4.6. withHeader(String key, StringValuePattern headerPattern)
Сигнатура: public MappingBuilder withHeader(String key, StringValuePattern headerPattern)
Что делает: Добавляет условие на наличие и значение HTTP-заголовка в запросе.
stubFor(get(urlEqualTo("/api/secure"))
.withHeader("Authorization", equalTo("Bearer my-token-123"))
.willReturn(aResponse().withStatus(200).withBody("Access granted")));
// Проверка, что заголовок присутствует (значение не важно)
stubFor(get(urlEqualTo("/api/track"))
.withHeader("X-Request-Id", matching(".*"))
.willReturn(aResponse().withStatus(200)));
4.7. verify(RequestPatternBuilder requestPatternBuilder)
Сигнатура: public static void verify(RequestPatternBuilder requestPatternBuilder)
Что делает: Проверяет, что определённый запрос был получен сервером. Это ключевой метод для верификации поведения клиента (например, что он отправил правильный запрос).
// После выполнения тестируемого кода...
// Проверяем, что был ровно 1 POST-запрос на /api/users
verify(postRequestedFor(urlEqualTo("/api/users")));
// Проверяем, что было 2 или более GET-запроса на /api/status
verify(2, getRequestedFor(urlEqualTo("/api/status")));
// Проверяем, что запрос содержал определённый заголовок
verify(postRequestedFor(urlEqualTo("/api/data"))
.withHeader("Content-Type", equalTo("application/json")));
4.8. reset()
Сигнатура: public static void reset()
Что делает: Сбрасывает все зарегистрированные заглушки и историю запросов. Обычно вызывается в методе @BeforeEach (JUnit 5) или @Before (JUnit 4), чтобы каждый тест начинался с "чистого листа".
import org.junit.jupiter.api.BeforeEach;
public class MyTest {
@BeforeEach
public void setUp() {
WireMock.reset(); // Очищаем все заглушки и историю перед каждым тестом
}
// ... тесты
}
4.9. WireMockServer(WireMockConfiguration options)
Сигнатура: public WireMockServer(WireMockConfiguration options)
Что делает: Создаёт экземпляр сервера с расширенными настройками: порт, HTTPS, файлы-заглушки, расширения и т.д.
// Создаём сервер на случайном порту с HTTPS
WireMockServer server = new WireMockServer(
WireMockConfiguration.options()
.dynamicPort()
.httpsSettings(new HttpsSettings(8443))
);
server.start();
// Получить динамический порт
int port = server.port();
System.out.println("Server running on port: " + port);
4.10. saveMappings()
Сигнатура: public void saveMappings()
Что делает: Сохраняет все текущие заглушки в файл (в директорию mappings). Полезно для записи (recording) реальных API.
// После того, как были зарегистрированы заглушки
wireMockServer.saveMappings();
// Файлы будут сохранены в папку mappings/ в рабочей директории
5. Реальный практический пример
Представим, что мы разрабатываем сервис для интернет-магазина, который зависит от внешнего API доставки. Нам нужно протестировать логику расчёта стоимости доставки. Внешний API может быть недоступен или медленным,
Также в библиотеке
JWT токены в Go (Golang): библиотека golang-jwt и примеры использования
Elasticsearch клиент для C# (.NET): NEST и Elastic.Clients.Elasticsearch
Валидация данных в Swift (iOS/macOS): инструменты и практика
Работа с YAML в C++: библиотеки yaml-cpp и rapidyaml