简体   繁体   English

Polly 重试单元测试

[英]Polly retry unit test

I am using polly to handle retry (see below code).我正在使用 polly 来处理重试(见下面的代码)。 How can I unit test polly retry?我如何对 polly 进行单元测试重试? using xunit and moq使用 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)
}));

You can unit test this by mocking out the HttpClient and setting up your own test version of the WaitAndRetryAsync policy.您可以通过模拟 HttpClient 并设置您自己的 WaitAndRetryAsync 策略测试版本来对此进行单元测试。 Example:例子:

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);

As suggested in the comments I recommend Simmy .正如评论中所建议的,我推荐Simmy

It allows you to inject exceptions, return BadRequests and etc. in order to trigger Polly's fault and resilience policies such as WaitAndRetry .它允许您注入异常、返回BadRequests等,以触发 Polly 的故障和弹性策略,例如WaitAndRetry

These are a few samples from the documentation .这些是文档中的一些示例。

Inject (Socket) exception注入(套接字)异常

var chaosPolicy = MonkeyPolicy.InjectException(Action<InjectOutcomeOptions<Exception>>);

For example: it causes the policy to throw SocketException with a probability of 5% if enabled例如:如果启用,它会导致策略以 5% 的概率抛出SocketException

var fault = new SocketException(errorCode: 10013);
var chaosPolicy = MonkeyPolicy.InjectException(with =>
    with.Fault(fault)
        .InjectionRate(0.05)
        .Enabled()
    );

Inject (BadRequest) result注入(BadRequest)结果

var chaosPolicy = MonkeyPolicy.InjectResult(Action<InjectOutcomeOptions<TResult>>);

For example: it causes the policy to return a bad request HttpResponseMessage with a probability of 5% if enabled例如:如果启用,它会导致策略以 5% 的概率返回错误的请求 HttpResponseMessage

var result = new HttpResponseMessage(HttpStatusCode.BadRequest);
var chaosPolicy = MonkeyPolicy.InjectResult<HttpResponseMessage>(with =>
    with.Result(result)
        .InjectionRate(0.05)
        .Enabled()
);

Simply set the InjectionRate to 1 to guarantee a fault in your unit test.只需将InjectionRate设置为1即可保证单元测试中出现错误。


If you want to use the InjectionRate less than 1 you can use xunit and moq chaining via SetupSequence and Moq.Language.ISetupSequentialResult .如果您想使用小于 1 的InjectionRate ,您可以通过SetupSequenceMoq.Language.ISetupSequentialResult使用 xunit 和 moq 链接。 Here's an example from an blockchain challenge I had to do, I execute 4 calls in a row, so if the InjectionRate is 0.25 one of the 4 calls would trigger a Polly policy:这是我必须做的区块链挑战的示例,我连续执行 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM