Polly 在单元测试中验证 httpclient timed out/retried 的次数
Polly verify how many times httpclient timed out/retried in a Unit Test
这不是一个完整的解决方案,只是使问题更清晰、更易读的部分代码。
我已经阅读了 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 的模拟延迟 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 次。
感谢您的任何回答。
问题是发布的测试代码不是 return 可取消的任务,模拟值是 SendAsync
到 return。在这方面,它并没有反映 HttpClient.SendAsync(...)
的行为方式。
Polly 超时策略默认 TimeoutStrategy.Optimistic
.
详情:
所有 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] 将 运行 在调用线程 上完全同步(整整四秒),直到它们 return 完成 Task
return 编辑 Task.FromResult
。他们在任何时候都不会 return a 'hot' (运行ning) Task
TimeoutPolicy 可以管理 - 调用策略在同步 four-second 之前不会重新获得控制权执行完毕。 [1] 和 [2] 不响应任何 CancellationToken
。所以调用超时策略永远不会超时。
Afaik there is no CallbackAsync(...) in Moq at this time,所以你需要将Thread.Sleep
移动到Returns(() => )
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
具有取消 运行ning Task
的控制权。
这不是一个完整的解决方案,只是使问题更清晰、更易读的部分代码。
我已经阅读了 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 的模拟延迟 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 次。 感谢您的任何回答。
问题是发布的测试代码不是 return 可取消的任务,模拟值是 SendAsync
到 return。在这方面,它并没有反映 HttpClient.SendAsync(...)
的行为方式。
Polly 超时策略默认 TimeoutStrategy.Optimistic
详情:
所有 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] 将 运行 在调用线程 上完全同步(整整四秒),直到它们 return 完成 Task
return 编辑 Task.FromResult
。他们在任何时候都不会 return a 'hot' (运行ning) Task
TimeoutPolicy 可以管理 - 调用策略在同步 four-second 之前不会重新获得控制权执行完毕。 [1] 和 [2] 不响应任何 CancellationToken
。所以调用超时策略永远不会超时。
Afaik there is no CallbackAsync(...) in Moq at this time,所以你需要将Thread.Sleep
移动到Returns(() => )
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
具有取消 运行ning Task
的控制权。