上面的答案对让我走上正轨非常有帮助。但是,我想测试是否已将策略添加到类型化的 http 客户端。此客户端是在应用程序启动时定义的。因此,挑战在于如何在类型化客户端定义中指定的处理程序之后添加一个存根委托处理程序,并将其添加到服务集合中。
我能够利用 IHttpMessageHandlerBuilderFilter.Configure 并将我的存根处理程序添加为链中的最后一个处理程序。
public sealed class HttpClientInterceptionFilter : IHttpMessageHandlerBuilderFilter
{
HandlerConfig handlerconfig { get; set; }
public HttpClientInterceptionFilter(HandlerConfig calls)
{
handlerconfig = calls;
}
/// <inheritdoc/>
public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
{
return (builder) =>
{
// Run any actions the application has configured for itself
next(builder);
// Add the interceptor as the last message handler
builder.AdditionalHandlers.Add(new StubDelegatingHandler(handlerconfig));
};
}
}
在你的单元测试中用 DI 容器注册这个类:
services.AddTransient<IHttpMessageHandlerBuilderFilter>(n => new HttpClientInterceptionFilter(handlerConfig));
我需要将一些参数传递给存根处理程序并从中获取数据并返回到我的单元测试。我使用这个类来做到这一点:
public class HandlerConfig
{
public int CallCount { get; set; }
public DateTime[] CallTimes { get; set; }
public int BackOffSeconds { get; set; }
public ErrorTypeEnum ErrorType { get; set; }
}
public enum ErrorTypeEnum
{
Transient,
TooManyRequests
}
我的存根处理程序生成瞬态和过多的请求响应:
public class StubDelegatingHandler : DelegatingHandler
{
private HandlerConfig _config;
HttpStatusCode[] TransientErrors = new HttpStatusCode[] { HttpStatusCode.RequestTimeout, HttpStatusCode.InternalServerError, HttpStatusCode.OK };
public StubDelegatingHandler(HandlerConfig config)
{
_config = config;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
_config.CallTimes[_config.CallCount] = DateTime.Now;
if (_config.ErrorType == ErrorTypeEnum.Transient)
{
var response = new HttpResponseMessage(TransientErrors[_config.CallCount]);
_config.CallCount++;
return Task.FromResult(response);
}
HttpResponseMessage response429;
if (_config.CallCount < 2)
{
//generate 429 errors
response429 = new HttpResponseMessage(HttpStatusCode.TooManyRequests);
response429.Headers.Date = DateTime.UtcNow;
DateTimeOffset dateTimeOffSet = DateTimeOffset.UtcNow.Add(new TimeSpan(0, 0, 5));
long resetDateTime = dateTimeOffSet.ToUnixTimeSeconds();
response429.Headers.Add("x-rate-limit-reset", resetDateTime.ToString());
}
else
{
response429 = new HttpResponseMessage(HttpStatusCode.OK);
}
_config.CallCount++;
return Task.FromResult(response429);
}
}
最后是单元测试:
[TestMethod]
public async Task Given_A_429_Retry_Policy_Has_Been_Registered_For_A_HttpClient_When_429_Errors_Occur_Then_The_Request_Is_Retried()
{
// Arrange
IServiceCollection services = new ServiceCollection();
var handlerConfig = new HandlerConfig { ErrorType = ErrorTypeEnum.TooManyRequests, BackOffSeconds = 5, CallTimes = new System.DateTime[RetryCount] };
// this registers a stub message handler that returns the desired error codes
services.AddTransient<IHttpMessageHandlerBuilderFilter>(n => new HttpClientInterceptionFilter(handlerConfig));
services.ConfigureAPIClient(); //this is an extension method that adds a typed client to the services collection
HttpClient configuredClient =
services
.BuildServiceProvider()
.GetRequiredService<IHttpClientFactory>()
.CreateClient("APIClient"); //Note this must be the same name used in ConfigureAPIClient
// Act
var result = await configuredClient.GetAsync("https://localhost/test");
// Assert
Assert.AreEqual(3, handlerConfig.CallCount, "Expected number of calls made");
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Verfiy status code");
var actualWaitTime = handlerConfig.CallTimes[1] - handlerConfig.CallTimes[0];
var expectedWaitTime = handlerConfig.BackOffSeconds + 1; //ConfigureAPIClient adds one second to give a little buffer
Assert.AreEqual(expectedWaitTime, actualWaitTime.Seconds);
}
}