Post

HTTP-интеграции в тестах: MockWebServer и WireMock

HTTP-интеграции в тестах: MockWebServer и WireMock

При тестировании сервиса, который выполняет HTTP-запросы во внешние системы, возникает базовая проблема: как проверить поведение без зависимости от реального внешнего API.

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

По этой причине в тестах внешний сервис не используется напрямую — вместо него поднимается имитация, полностью контролируемая тестом.

Общая идея

Вместо реального API поднимается локальный HTTP-сервер, который возвращает заранее заданные ответы.

1
Service → HTTP → Fake Server

Такой сервер полностью контролируется тестом: можно задавать статус-коды, тело ответа, задержки и ошибки.

Существует два распространенных инструмента:

  • MockWebServer
  • WireMock

MockWebServer: сценарный подход

MockWebServer — это HTTP-сервер, который возвращает заранее подготовленные ответы в заданной последовательности, это полностью управляемая HTTP-среда для теста.

Такой подход удобен для проверки поведения HTTP-клиента: обработки ошибок, таймаутов, повторных попыток (retry), а также порядка запросов. При этом он не предназначен для тестирования бизнес-логики сервиса — его задача ограничивается моделированием низкоуровневого взаимодействия по HTTP.

Ключевая идея:

1
Клиент направляется на mock-сервер → все HTTP-вызовы уходят в него

Полный пример

В данном тесте:

  • Поднимается локальный HTTP-сервер;
  • Клиент явно направляется на него через baseUrl;
  • Сервер последовательно возвращает подготовленные ответы;
  • Проверяется, как клиент ведет себя при ошибках, задержках и retry.

Рассмотрим пример.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
MockWebServer server = new MockWebServer();
server.start();

// 1. Подготавливается поведение сервера (очередь ответов)
server.enqueue(new MockResponse().setResponseCode(503)); // ошибка
server.enqueue(new MockResponse() // таймаут (задержка)
        .setBody("{\"message\":\"retry\"}")
        .setBodyDelay(1500, TimeUnit.MILLISECONDS));
server.enqueue(new MockResponse() // успешный ответ
        .setBody("{\"message\":\"success\"}"));

// 2. Клиент направляется на mock-сервер
WebClient client = WebClient.builder()
        .baseUrl(server.url("/").toString()) // ← ключевой момент
        .build();

// 3. Выполняется HTTP-вызов
Mono<String> response = client.get()
        .uri("/test") // реальный URL будет: http://localhost:<port>/test
        .retrieve()
        .bodyToMono(String.class)
        .retry(2); // клиент сам делает повторные попытки


// 4. Проверка результата
StepVerifier.create(response)
        .expectNext("{\"message\":\"success\"}")
        .verifyComplete();

Что здесь происходит

1
2
3
1-й запрос → 503 → retry  
2-й запрос → задержка → timeout → retry  
3-й запрос → success  

Проверка HTTP-вызовов

Дополнительно можно убедиться, что запросы действительно дошли до mock-сервера:

1
2
3
4
5
6
7
RecordedRequest request1 = server.takeRequest();
RecordedRequest request2 = server.takeRequest();
RecordedRequest request3 = server.takeRequest();

assertThat(request1.getPath()).isEqualTo("/test");
assertThat(request2.getPath()).isEqualTo("/test");
assertThat(request3.getPath()).isEqualTo("/test");

Область применения

MockWebServer подходит для тестирования:

  • retry и timeout логики;
  • Последовательности HTTP-вызовов;
  • Низкоуровневого поведения клиента.

WireMock: декларативный подход

WireMock работает по другому принципу. Вместо очереди ответов задаются правила: какой ответ должен быть возвращен при определенном запросе.

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

Пример

В этом примере:

  • Поднимается локальный HTTP-сервер WireMock;
  • Клиент явно направляется на него через baseUrl;
  • Задается ожидаемое поведение внешнего API;
  • Выполняется вызов и проверяется результат;
  • При необходимости проверяется, что запрос действительно был отправлен.

В отличие от MockWebServer, здесь проверяется не последовательность ответов, а корректность обработки конкретного HTTP-контракта.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RegisterExtension
static WireMockExtension wiremock = WireMockExtension.newInstance()
        .options(wireMockConfig().dynamicPort())
        .build();

// клиент должен быть направлен на WireMock
WebClient webClient = WebClient.builder()
        .baseUrl(wiremock.baseUrl()) // ← ключевой момент
        .build();

// описывается поведение внешнего API
wiremock.stubFor(post("/resolve")
        .willReturn(okJson("""
            {
              "items": [
                { "name": "Pizza", "price": 10.5, "available": true }
              ]
            }
        """)));

// выполняется HTTP-вызов
Mono<Response> response = webClient.post()
        .uri("/resolve")
        .retrieve()
        .bodyToMono(Response.class);

// проверяется результат
StepVerifier.create(response)
        .expectNextMatches(r -> r.getItems().size() == 1)
        .verifyComplete();

// при необходимости проверяется сам факт вызова
wiremock.verify(postRequestedFor(urlEqualTo("/resolve")));

Область применения

WireMock используется для:

  • Интеграционного тестирования сервисов;
  • Проверки бизнес-логики;
  • Имитации внешних API на уровне контрактов.

Ключевое различие

1
2
MockWebServer → сценарии (очередь ответов)
WireMock      → поведение (правила обработки запросов)

MockWebServer удобен для проверки последовательности событий, WireMock — для описания API и работы с ним.

Независимость от стека

Оба инструмента работают на уровне HTTP и не зависят от используемой технологии (Spring WebFlux, Spring MVC, любой HTTP-клиент). Реактивная модель не является обязательной.

Что именно тестируется

Важно понимать границы таких тестов.

Проверяется не реальное взаимодействие двух сервисов, а поведение тестируемого сервиса при различных ответах внешней системы.

1
Service → fake external API

Это позволяет детерминировать поведение, воспроизводить ошибки, тестировать крайние сценарии (timeouts, 5xx, частичные ответы).

Тестирование HTTP-слоя внутри приложения

Отдельный сценарий — тестирование не внешних интеграций, а собственного HTTP API.

В этом случае поднимается само приложение, и запросы отправляются уже в него:

1
2
3
4
5
6
7
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class OrderControllerTest {

    @Autowired
    private WebTestClient webTestClient;
}

Здесь поднимается реальный HTTP-сервер приложения (на случайном порту). Данная аннотация запускает полноценный Spring Boot контекст вместе с embedded сервером:

1
@SpringBootTest(webEnvironment = RANDOM_PORT)

Предоставляет клиент для отправки HTTP-запросов в этот сервер:

1
@AutoConfigureWebTestClient

Пример вызова

1
2
3
4
5
webTestClient.post()
        .uri("/api/orders")
        .bodyValue(request)
        .exchange()
        .expectStatus().isCreated();

Здесь тестируется HTTP слой самого сервиса (controller → service → repository)

В отличие от предыдущих подходов:

  • Здесь нет имитации HTTP-сервера — используется реальный сервер приложения;
  • Не проверяется внешний API — тестируется собственный;
  • Это не unit-тест, а полноценный integration-тест внутри одного сервиса.

Связь с WireMock

Эти подходы часто используются вместе. Входящие запросы тестируются через WebTestClient, исходящие — подменяются через WireMock.

1
входящий HTTP → реальный сервер (SpringBootTest) исходящий HTTP → WireMock

Итог

1
2
3
MockWebServer → контроль последовательности HTTP-ответов
WireMock      → имитация поведения внешнего API
SpringBootTest + WebTestClient → тестирование собственного HTTP API

Эти подходы решают одну задачу — изолировать тесты от внешних факторов — но применяются на разных уровнях.

MockWebServer используется для проверки поведения HTTP-клиента и сценариев взаимодействия, WireMock — для тестирования логики работы с внешним API, а SpringBootTest с WebTestClient — для проверки собственного HTTP-интерфейса приложения.

В совокупности они позволяют выстроить предсказуемую и управляемую тестовую среду без зависимости от реальных внешних систем.

This post is licensed under CC BY 4.0 by the author.