2

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

我在这里阅读了 Polly 的文档,但是我确实需要在 httpClient 超时后测试是否命中了以下 TimeoutPolicy 的委托(另外,如果将TimeoutPolicyonTimeoutAsync与 RetryPolicy 一起包装 -它被命中了多少次):

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() 以进行断言:

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

4

1 回答 1

4

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

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

Afaik目前在 Moq 中没有 CallbackAsync(...),因此您需要将 移动Thread.SleepReturns(() => )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")
        };
    });

在此代码中, TimeoutPolicyawait在 [3] 处重新获得控制权,并且cancellationToken具有取消运行的控制权Task

于 2018-03-22T19:40:28.720 回答