2

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");
         ...
    }
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
itaustralia
  • 127
  • 1
  • 12
  • Where are you using productService in controller? – Maruf Jul 23 '22 at 06:56
  • Could you describe your test case(s) with [Given When Then](https://martinfowler.com/bliki/GivenWhenThen.html) structure? Because *unit test endpoint and retry policy* does not tell anything about your testing intent. – Peter Csala Jul 23 '22 at 13:13
  • Given= GetProducts is called, When it return error 5XX error Then retry 3 times – itaustralia Jul 24 '22 at 04:41
  • a workaround would be add an instance of the policy to DI container during startup and inject it into your service. Than you could `retryPolicy.Policy.ExecuteAsync(async () => await client.GetAsync("uri"))`. you could mock the http side to respond with http codes in a sequence , and capture the request bodies to test that it posts as many times as you expect it to – mishal153 Aug 28 '23 at 08:05

1 Answers1

2

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.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75