[英]Polly retry unit test
我正在使用 polly 来处理重试(见下面的代码)。 我如何对 polly 进行单元测试重试? 使用 xunit 和最小起订量
services.AddHttpClient("GitHub", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
})
.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10)
}));
您可以通过模拟 HttpClient 并设置您自己的 WaitAndRetryAsync 策略测试版本来对此进行单元测试。 例子:
var mockHttpClient = new Mock<HttpClient>();
var mockRetryPolicy = new Mock<IAsyncPolicy<HttpResponseMessage>>();
mockRetryPolicy
.Setup(p => p.ExecuteAsync(It.IsAny<Func<Context, CancellationToken, Task<HttpResponseMessage>>>(), It.IsAny<Context>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new HttpResponseMessage());
var services = new ServiceCollection();
services
.AddHttpClient("GitHub", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
})
.AddTransientHttpErrorPolicy(builder => mockRetryPolicy.Object);
var serviceProvider = services.BuildServiceProvider();
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("GitHub");
Assert.NotNull(httpClient);
正如评论中所建议的,我推荐Simmy 。
它允许您注入异常、返回BadRequests
等,以触发 Polly 的故障和弹性策略,例如WaitAndRetry
。
这些是文档中的一些示例。
注入(套接字)异常
var chaosPolicy = MonkeyPolicy.InjectException(Action<InjectOutcomeOptions<Exception>>);
例如:如果启用,它会导致策略以 5% 的概率抛出SocketException
var fault = new SocketException(errorCode: 10013);
var chaosPolicy = MonkeyPolicy.InjectException(with =>
with.Fault(fault)
.InjectionRate(0.05)
.Enabled()
);
注入(BadRequest)结果
var chaosPolicy = MonkeyPolicy.InjectResult(Action<InjectOutcomeOptions<TResult>>);
例如:如果启用,它会导致策略以 5% 的概率返回错误的请求 HttpResponseMessage
var result = new HttpResponseMessage(HttpStatusCode.BadRequest);
var chaosPolicy = MonkeyPolicy.InjectResult<HttpResponseMessage>(with =>
with.Result(result)
.InjectionRate(0.05)
.Enabled()
);
只需将InjectionRate
设置为1
即可保证单元测试中出现错误。
如果您想使用小于 1 的InjectionRate
,您可以通过SetupSequence
和Moq.Language.ISetupSequentialResult
使用 xunit 和 moq 链接。 这是我必须做的区块链挑战的示例,我连续执行 4 个调用,因此如果 InjectionRate 为 0.25,则 4 个调用中的一个将触发 Polly 策略:
[Fact]
public async Task Should_Return_GetEthereumTransactionsAsync()
{
// Arrange
IConfiguration settings = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
IOptions<Settings> optionSettings = Options.Create(new Settings
{
CompanyKeyAPI = settings.GetSection("CompanyKeyAPI").Value,
ProjectId = settings.GetSection("ProjectId").Value
});
var sequenceHttpResponse = new List<Tuple<HttpStatusCode, HttpContent>>
{
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.EthereumBlockWithTransactionHashs()),
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.Transaction(1)),
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.Transaction(2)),
new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, ApiCompanyKeyResponses.Transaction(3))
};
IHttpClientFactory httpClientFactory = base.GetChainedCompanyKeyHttpClientFactory(new Uri(Path.Combine(optionSettings.Value.CompanyKeyAPI, optionSettings.Value.ProjectId)), sequenceHttpResponse);
CompanyKeyService CompanyKeyService = new CompanyKeyService(httpClientFactory);
// Act
List<EthereumTransaction> ethereumTransactionsResult = CompanyKeyService.GetEthereumTransactionsAsync(blockNumber, address).Result;
// Assert
Assert.IsType<List<EthereumTransaction>>(ethereumTransactionsResult);
Assert.NotNull(ethereumTransactionsResult);
Assert.Equal(ethereumTransactionsResult.Count, 3);
Assert.Equal(ethereumTransactionsResult[0].result.blockHash, blockHash);
}
public IHttpClientFactory GetChainedCompanyKeyHttpClientFactory(Uri uri, List<Tuple<HttpStatusCode, HttpContent>> httpReturns, HttpStatusCode statusCode = HttpStatusCode.OK)
{
Mock<HttpMessageHandler> httpMsgHandler = new Mock<HttpMessageHandler>();
var handlerPart = httpMsgHandler.Protected().SetupSequence<Task<HttpResponseMessage>>("SendAsync", new object[2]
{
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
});
foreach (var httpResult in httpReturns)
{
handlerPart = AddReturnPart(handlerPart, httpResult.Item1, httpResult.Item2);
}
httpMsgHandler.Verify();
HttpClient client = new HttpClient(httpMsgHandler.Object)
{
BaseAddress = uri
};
Mock<IHttpClientFactory> clientFactory = new Mock<IHttpClientFactory>();
clientFactory.Setup((IHttpClientFactory cf) => cf.CreateClient(It.IsAny<string>())).Returns(client);
return clientFactory.Object;
}
private Moq.Language.ISetupSequentialResult<Task<HttpResponseMessage>> AddReturnPart(Moq.Language.ISetupSequentialResult<Task<HttpResponseMessage>> handlerPart,
HttpStatusCode statusCode, HttpContent content)
{
return handlerPart
// prepare the expected response of the mocked http call
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = statusCode,
Content = content
});
}
....
public class CompanyKeyService : ICompanyKeyService
{
private readonly IHttpClientFactory _clientFactory;
private readonly HttpClient _client;
public CompanyKeyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
_client = _clientFactory.CreateClient("GitHub");
}
public async Task<List<EthereumTransaction>> GetEthereumTransactionsAsync(string blockNumber, string address)
{
//Validation removed...
List<string> transactionHashs = await GetEthereumTransactionHashsByBlockNumberAsync(blockNumber);
if (transactionHashs == null) throw new Exception("Invalid entry. Please check the Block Number.");
var tasks = transactionHashs.Select(hash => GetTransactionByHashAsync(hash, address));
EthereumTransaction[] lists = await Task.WhenAll(tasks);
return lists.Where(item => item != null).ToList();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.