Home > Back-end >  retry polly unit testing (xunit and moq)
retry polly unit testing (xunit and moq)

Time:07-24

I have .net core weabpi (see code below). I am using polly retry policy (see policy below). I would like to unit test endpoint (getProducts) and test polly retry

I have found these examples but it is not clear how to unit test endpoint and retry policy?

services
  .AddHttpClient<IProductService, ProductService>()
  .AddPolicyHandler(GetRetryPolicy(3, 2)); 

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(int retryCount, int breakDuration)
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(breakDuration,
            retryAttempt)));
}

.Net core api:

public interface IProductService
{
    Task<IEnumerable<ProductResponse>> GetProducts(string productType);
}
public class ProductService: IProductService
{ 
    private readonly HttpClient _httpClient;
    
    public ProductService(HttpClient httpClient)
    {
         _httpClient = httpClient;
    }
    
    public async Task<IEnumerable<ProductResponse>> GetProducts(string productType)
    {
         var response = await _httpClient.GetAsync("uri");
         ...
    }
}

CodePudding user response:

First

I would suggest you add Wait and Retry with Jittered Back-off to your policy.

Something like that:

var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(100), policyRetryCount);

            RetryPolicy retryPolicy = Policy
                            .Handle<YourException>()
                            .WaitAndRetry(delay);

Then, just Mock your service and test it, like:

Mock<IProductService> mockedProductService = new Mock<IProductService>();

mockedProductService.Setup(service => service.GetProducts(productType))
                                             .ThrowsYourException();

var result = yourTestingMehtod

Assert.IsNotNull(result);
mockedProductService.Verify(service => service.GetProducts(productType), Times.Exactly(expectedServiceCall));
// example, expectedServiceCall is when you set retryCount 3, it will be 4

Update

I suppose you are calling your service like:

retryPolicy.Execute(() => productService.GetProducts())

CodePudding user response:

The sad truth is you can't really unit test your retry endpoint logic and here are my reasonings why:

  1. The retry is registered on the top of the HttpClient via the DI (AddPolicyHandler). When you are unit testing then you are not relying on the DI rather on individual components.
    1.1 So, an integration test might be more suitable for this. I've already detailed how can you do that via WireMock.Net: 1, 2. The basic idea is to create a local http server (to mock the downstream system) with a predefined response sequence.
  2. After you have defined your retry policy with the max retry count and time penalties, you can not retrieve them easily. So, from a unit testing perspective it is really hard to make sure that the policy has been defined correctly (like the delay is specified in seconds, not in minutes). I've already created a github issue for this, but unfortunately the development of the V8 got stuck.

Back to your test case. The correct way to articulate your test scenario with the given-when-then structure should be written like this

  • Given a faulty downstream service which returns 5XX responses
  • When I call GetProducts
  • Then it is performed 4 times (1 initial 3 retry attempts)

This is not a unit test. It is more like a component/integration test. Why? Because even though you could create an HttpClient mock but in that case there will be no retry policy there.

There is a workaround: you could manually decorate the underlying handler with the policy via the PolicyHttpMessageHandler. But that's a bad idea in a unit test, because you basically re-implemented the DI part inside your test. And with that you would test your test arrangement code, not your production code.

  • Related