Модульный тест HttpClient с Полли - PullRequest
0 голосов
/ 16 апреля 2020

Я пытаюсь выполнить модульное тестирование HttpClient с Polly RetryPolicy и пытаюсь выяснить, как управлять ответом HTTP.

Я использовал HttpMessageHandler на клиенте и затем переопределите Send Asyn c, и это прекрасно работает, но когда я добавляю Polly Retry Policy, я должен создать экземпляр Http-клиента с помощью IServiceCollection и не могу создать HttpMessageHandler для клиента. Я попытался использовать .AddHttpMessageHandler(), но затем он блокирует политику повторных запросов на опрос, и она срабатывает только один раз.

Так я настраиваю свой http-клиент в своем тесте

IServiceCollection services = new ServiceCollection();

const string TestClient = "TestClient";

services.AddHttpClient(name: TestClient)
         .AddHttpMessageHandler()
         .SetHandlerLifetime(TimeSpan.FromMinutes(5))
         .AddPolicyHandler(KYA_GroupService.ProductMessage.ProductMessageHandler.GetRetryPolicy());

HttpClient configuredClient =
                services
                    .BuildServiceProvider()
                    .GetRequiredService<IHttpClientFactory>()
                    .CreateClient(TestClient);

public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
            .HandleTransientHttpError()
            .WaitAndRetryAsync(6,
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetryAsync: OnRetryAsync);
}

private async static Task OnRetryAsync(DelegateResult<HttpResponseMessage> outcome, TimeSpan timespan, int retryCount, Context context)
{
    //Log result
}

Это затем вызовет запрос, когда я позвоню _httpClient.SendAsync(httpRequestMessage), но он действительно создаст Http-вызов по адресу, и мне нужно перехватить это каким-то образом и вернуть контролируемый ответ.

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

Основное ограничение, которое у меня есть, - я не могу использовать Moq на MSTest.

1 Ответ

0 голосов
/ 16 апреля 2020

Вы не хотите, чтобы ваш HttpClient выдавал реальные HTTP-запросы как часть модульного теста - это было бы интеграционным тестом. Чтобы не делать реальных запросов, вам необходимо предоставить пользовательский HttpMessageHandler. Вы указали в своем посте, что не хотите использовать насмешливую структуру, поэтому вместо насмешки HttpMessageHandler вы можете предоставить заглушку .

с сильным влиянием от этот комментарий к проблеме на странице GitHub Полли. Я настроил ваш пример так, чтобы вызывать заглушку HttpMessageHandler, которая выбрасывает 500 при первом вызове, а затем возвращает 200 при последующих запросах.

В тесте утверждается, что обработчик повторных попыток вызван и что при выполнении шагов после вызова к HttpClient.SendAsync результирующий ответ имеет код состояния 200:

public class HttpClient_Polly_Test
{
    const string TestClient = "TestClient";
    private bool _isRetryCalled;

    [Fact]
    public async Task Given_A_Retry_Policy_Has_Been_Registered_For_A_HttpClient_When_The_HttpRequest_Fails_Then_The_Request_Is_Retried()
    {
        // Arrange 
        IServiceCollection services = new ServiceCollection();
        _isRetryCalled = false;

        services.AddHttpClient(TestClient)
            .AddPolicyHandler(GetRetryPolicy())
            .AddHttpMessageHandler(() => new StubDelegatingHandler());

        HttpClient configuredClient =
            services
                .BuildServiceProvider()
                .GetRequiredService<IHttpClientFactory>()
                .CreateClient(TestClient);

        // Act
        var result = await configuredClient.GetAsync("https://www.stackoverflow.com");

        // Assert
        Assert.True(_isRetryCalled);
        Assert.Equal(HttpStatusCode.OK, result.StatusCode);
    }

    public IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
    {
        return HttpPolicyExtensions.HandleTransientHttpError()
            .WaitAndRetryAsync(
                6,
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetryAsync: OnRetryAsync);
    }

    private async Task OnRetryAsync(DelegateResult<HttpResponseMessage> outcome, TimeSpan timespan, int retryCount, Context context)
    {
        //Log result
        _isRetryCalled = true;
    }
}

public class StubDelegatingHandler : DelegatingHandler
{
    private int _count = 0;

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (_count == 0)
        {
            _count++;
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));
        }

        return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
    }
}
...