【问题标题】:Polly verify how many times httpclient timed out/retried in a Unit TestPolly 验证在单元测试中 httpclient 超时/重试了多少次
【发布时间】:2018-03-22 12:59:53
【问题描述】:

这不是一个完全有效的解决方案,只是部分代码使问题更清晰、更具可读性。

我已经阅读了 Polly 的文档 here,但是我确实需要测试以下 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 的 mock 延迟 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() 以进行断言:

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 次。 感谢您的任何回答。

【问题讨论】:

    标签: unit-testing timeout dotnet-httpclient assertions polly


    【解决方案1】:

    问题是发布的测试代码没有返回可取消的任务,在模拟SendAsync 返回的值。在这方面,它并没有反映 HttpClient.SendAsync(...) 的行为方式。

    和 Polly 超时策略,默认为 TimeoutStrategy.Optimistic operates by timing-out CancellationToken, so the delegates you execute must respond to co-operative cancellation


    详情:

    所有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.FromResult 返回的完整的Task。他们绝不会返回 TimeoutPolicy 可以管理的“热”(正在运行)Task - 调用策略在同步的四秒执行完成之前不会重新获得控制权。并且 [1] 和 [2] 不回复任何CancellationToken。所以调用超时策略永远不会超时。

    Afaik there is no CallbackAsync(...) in Moq at this time,因此您需要将Thread.Sleep 移动到Returns(() =&gt; ) lambda 表达式中,作为可取消的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 在 [3] 处重新获得await 的控制权,而cancellationToken 拥有取消正在运行的Task 的控制权。

    【讨论】:

    猜你喜欢
    • 2022-07-25
    • 1970-01-01
    • 1970-01-01
    • 2020-08-09
    • 2017-03-03
    • 1970-01-01
    • 1970-01-01
    • 2010-09-05
    • 1970-01-01
    相关资源
    最近更新 更多