Polly проверьте, сколько раз httpclient превышал время ожидания / повторял попытки в модульном тесте

Это не полностью рабочее решение, это просто часть кода, чтобы вопрос был более понятным и читабельным.

Я прочитал документацию Полли здесь, однако мне действительно нужно проверить, действительно ли следующий делегат TimeoutPolicy onTimeoutAsync выполняется (также, в случае обертывания TimeoutPolicy вместе с RetryPolicy - сколько TIMES было выполнено) после истечения времени ожидания httpClient:

public static TimeoutPolicy<HttpResponseMessage> TimeoutPolicy
{
    get
    {
        return Policy.TimeoutAsync<HttpResponseMessage>(1, onTimeoutAsync: (context, timeSpan, task) =>
        {
            Console.WriteLine("Timeout delegate fired after " + timeSpan.TotalMilliseconds);
            return Task.CompletedTask;
        });
    }
}

TimeoutPolicy установлен на тайм-аут через 1 секунду после того, как HTTP-клиент не получил ничего (макет HttpClient задерживается на 4 секунды, как показано ниже)

var httpMessageHandler = new Mock<HttpMessageHandler>();

httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Callback(() => Thread.Sleep(4000))//Delayed here
.Returns(() => Task.FromResult(new HttpResponseMessage
{
    StatusCode = HttpStatusCode.InternalServerError,
    Content = new StringContent("Some response body", Encoding.UTF8, "text/xml")
}));

var httpClient = new HttpClient(httpMessageHandler.Object);

Политики имитируются, чтобы иметь возможность вызывать .Verify () для Assertion:

var mockedPolicies = new Mock<Policies>();

Я выполняю вызов с фиктивным HTTP-клиентом и фиктивной политикой тайм-аута:

await mockedPolicies.Object.TimeoutPolicy.ExecuteAsync(ct => _httpClient.PostAsync("", new StringContent("Some request body"), ct), CancellationToken.None);

Утверждение:

mockedPolicies.Verify(p => p.TimeoutDelegate(It.IsAny<Context>(), It.IsAny<TimeSpan>(), It.IsAny<Task>()), Times.Exactly(1));

Тем не менее, результат теста говорит, что он был вызван 0 раз вместо 1. Спасибо за любые ответы.


person Kal    schedule 22.03.2018    source источник


Ответы (1)


Проблема в том, что опубликованный тестовый код не возвращает отменяемую задачу со значением, имитируемым для возврата SendAsync. В этом аспекте он не отражает поведение HttpClient.SendAsync(...).

А политика времени ожидания Polly со значением по умолчанию TimeoutStrategy.Optimistic работает по тайм-ауту CancellationToken, поэтому выполняемые вами делегаты должны отвечать на совместную отмену .


Подробно:

Все async вызовы выполняются синхронно до первого await оператора. В опубликованном тестовом коде оба:

// [1]
.Callback(() => Thread.Sleep(4000))//Delayed here

а также:

// [2]
.Returns(() => Task.FromResult(new HttpResponseMessage
{
    StatusCode = HttpStatusCode.InternalServerError,
    Content = new StringContent("Some response body", Encoding.UTF8, " text/xml")
})

не содержат инструкции await. Таким образом, [1] и [2] будут выполняться полностью синхронно в вызывающем потоке (в течение полных четырех секунд), пока они не вернут завершенный Task, возвращенный Task.FromResult. Ни в коем случае они не возвращают «горячий» (рабочий) Task, которым может управлять TimeoutPolicy - политика вызова не восстанавливает управление до тех пор, пока не завершится синхронное четырехсекундное выполнение. И [1] и [2] не отвечают ни на один CancellationToken. Таким образом, политика тайм-аута вызова никогда не может истечь.

Afaik в настоящее время в Moq нет CallbackAsync (...), поэтому вам нужно переместить Thread.Sleep в Returns(() => ) лямбда-выражение в качестве отменяемого await Task.Delay(...), чтобы смоделировать поведение HttpClient.SendAsync(...). Что-то вроде этого:

httpMessageHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", 
        ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
    .Returns<object, CancellationToken>(async (request, cancellationToken) => {
        await Task.Delay(4000, cancellationToken); // [3]
        return new HttpResponseMessage
        {
            StatusCode = HttpStatusCode.InternalServerError,
            Content = new StringContent("Some response body", Encoding.UTF8, "text/xml")
        };
    });

В этом коде TimeoutPolicy восстанавливает управление в await в [3], а cancellationToken имеет управление для отмены выполнения Task.

person mountain traveller    schedule 22.03.2018
comment
См. Также эти прошлые выпуски Polly для более подробного объяснения того же вопроса, если это полезно: github.com / App-vNext / Polly / issues / 318; github.com/App-vNext/Polly/issues/340. Этот вопрос SO stackoverflow.com/questions/36583780 относится к Moq, который не предлагает CallbackAsync(...), как это делается в Moq Github: github.com/moq/moq4/issues/256 - person mountain traveller; 22.03.2018
comment
Спасибо за ваш ответ. Отличный контент от вас, как всегда! Мне пришлось использовать общий объект .Returns ‹, CancellationToken›, но в остальном макет HttpClient работает отлично и истекает время ожидания политики. - person Kal; 23.03.2018
comment
Спасибо @Kalnius. Обновлен ответ, чтобы добавить параметры универсального типа. - person mountain traveller; 23.03.2018