Моки внешних HTTP-сервисов: WireMock.Net, HttpMessageHandler
↑ 5.7 Тестирование интеграций · → Следующая
Зачем это на собесе
Как тестировать код, вызывающий внешние API, без реальных вызовов и нестабильности.
Объяснение
| Способ | Что это | Когда |
|---|---|---|
Подмена HttpMessageHandler | fake-обработчик, возвращающий заготовленный ответ | unit-тесты клиента, без сети |
| WireMock.Net | настоящий HTTP-сервер-заглушка со сценариями | интеграционные тесты, проверка сериализации, заголовков, таймаутов |
| Контрактный тест (Pact) | проверка соглашения между потребителем и поставщиком | защита от расхождения контрактов |
| Sandbox провайдера | тестовая среда внешней системы | ручные/e2e проверки |
// WireMock.Net
using var server = WireMockServer.Start();
server.Given(Request.Create().WithPath("/v2/charges").UsingPost().WithHeader("Idempotency-Key", "*"))
.RespondWith(Response.Create().WithStatusCode(201).WithBodyAsJson(new { id = "ch_1", status = "succeeded" }));
var gateway = new AcmePaymentGateway(new HttpClient { BaseAddress = new Uri(server.Url!) });
var result = await gateway.ChargeAsync(charge, default);
Assert.Equal(PaymentStatus.Succeeded, result.Status);Сценарии сбоев (главная ценность моков):
server.Given(Request.Create().WithPath("/v2/charges").UsingPost())
.InScenario("retry").WillSetStateTo("second").RespondWith(Response.Create().WithStatusCode(503));
server.Given(Request.Create().WithPath("/v2/charges").UsingPost())
.InScenario("retry").WhenStateIs("second").RespondWith(Response.Create().WithStatusCode(201).WithBodyAsJson(ok));
// задержка: Response.Create().WithDelay(TimeSpan.FromSeconds(5)) — проверить таймаутFake handler:
class StubHandler(HttpResponseMessage response) : HttpMessageHandler
{
public HttpRequestMessage? Last;
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage r, CancellationToken ct) { Last = r; return Task.FromResult(response); }
}Нюансы и подводные камни
- Мок отражает ваши предположения: при изменении настоящего API тест остаётся зелёным. Дополняйте контрактными тестами и проверкой на sandbox.
- Проверяйте не только успех: 4xx/5xx, таймауты, некорректный JSON, лишние поля.
- Не мокайте
HttpClientчерезMock<HttpClient>: подменяйте handler. - В
WebApplicationFactoryвнешний адрес подставляйте через конфигурацию на URL WireMock. - Порты WireMock выбирайте динамически для параллельных тестов.
Практика
- Напишите тесты клиента платёжного API на WireMock: успех, 503→успех, таймаут, 400.
- Проверьте, что клиент отправляет
Idempotency-Keyи корректный JSON. - Запишите ответы реального API как заготовки (record/playback).
Вопросы с ответами
Чем WireMock лучше подмены HttpMessageHandler?
Проходит настоящий HTTP-стек (сериализация, заголовки, таймауты, ретраи) и позволяет описывать сценарии сбоев.
Какой недостаток у моков внешних API?
Они могут разойтись с реальностью; нужны контрактные тесты и периодическая проверка на sandbox.