Home > Net >  How do I pass ILogger to a static class, so it's suitable for unit tests as well
How do I pass ILogger to a static class, so it's suitable for unit tests as well

Time:05-26

How do I pass ILogger<T> to the static class Policies and its static method RateLimit(...), so it's suitable for both the Client.TooManyRequestsAsync and the PoliciesTests.Test1? The issue is that I cannot pass NullLogger.Instance in the unit tests.

public class PoliciesTests
{
    [Fact]
    public async Task Test1()
    {
        var client = new RestClient("https://httpstat.us/429");
        var request = new RestRequest();

        // TODO: Compile time error here
        var response = await Policies.RateLimit(NullLogger.Instance).ExecuteAsync(() => client.ExecuteAsync(request));
    }
}

public class Client : IDisposable
{
    public Client(ILoggerFactory loggerFactory)
    {
        _restClient = new RestClient(restApiUrl);
        _logger = loggerFactory.CreateLogger<Client>();
    }

    public async Task TooManyRequestsAsync()
    {
        var client = new RestClient("https://httpstat.us/429");
        var request = new RestRequest();
        var response = await Policies.RateLimit(_logger).ExecuteAsync(() => client.ExecuteAsync(request));
    }
}

public static class Policies
{
    private const int RateLimitRetryCount = 2;

    public static AsyncRetryPolicy<RestResponse> RateLimit<T>(ILogger<T> logger)
    {
        return Policy.HandleResult<RestResponse>(response => response.StatusCode == HttpStatusCode.TooManyRequests)
            .WaitAndRetryAsync(RateLimitRetryCount,
                (attemptCount, restResponse, _) =>
                {
                    var retryAfterHeader = restResponse?.Result?.Headers?.SingleOrDefault(h => h.Name == "Retry-After");
                    double secondsInterval = 0;

                    if (retryAfterHeader != null)
                    {
                        var value = retryAfterHeader.Value?.ToString();
                        if (!double.TryParse(value, out secondsInterval))
                        {
                            secondsInterval = Math.Pow(2, attemptCount);
                        }
                    }

                    return TimeSpan.FromSeconds(secondsInterval);
                },
                (response, timeSpan, retryCount, _) =>
                {
                    logger.LogTrace(
                        "The API request has been rate limited. HttpStatusCode={StatusCode}. Waiting {Seconds} seconds before retry. Number attempt {RetryCount}. Uri={Url}; RequestResponse={Content}",
                        response.Result.StatusCode, timeSpan.TotalSeconds, retryCount, response.Result.ResponseUri, response.Result.Content);

                    return Task.CompletedTask;
                });
    }
}

CodePudding user response:

You can create an instance of ILogger using NullLoggerFactory.Instance.CreateLogger().

So your example will looks like:

public class PoliciesTests
{
    private readonly ILogger<PoliciesTests> _logger = 
        NullLoggerFactory.Instance.CreateLogger<PoliciesTests>();

    [Fact]
    public async Task Test1()
    {
        var client = new RestClient("https://httpstat.us/429");
        var request = new RestRequest();
        var response = await Policies.RateLimit(_logger).ExecuteAsync(() => client.ExecuteAsync(request)); // No compile time error
    }
}

CodePudding user response:

As per my understanding your implementation looks good, but the problem you are getting to using ILogger logger in test case. If this understanding is correct then you can just use Mock (using Moq;) library.

Then just mock the logger.LogTrace method.

Mostly this will resolve your problem var logger = new Mock<ILogger>();

Assuming you have installed moq library and implemented in the class

// using Moq;

[Fact]
public async Task Test1()
{
    var client = new RestClient("https://httpstat.us/429");
    var request = new RestRequest();
    
    // Using mock logger
    var logger = new Mock<ILogger>();
    logger.Setup(x=>x.LogTrace(It.IsAny<string>(),... like all parameters with type separated by comma))
    
    var response = await Policies.RateLimit(logger.Object).ExecuteAsync(() => client.ExecuteAsync(request));
}
  • Related