如何将 ILogger 传递给静态 class,因此它也适用于单元测试
How do I pass ILogger to a static class, so it's suitable for unit tests as well
如何将 ILogger<T>
传递给静态 class Policies
及其静态方法 RateLimit(...)
,因此它适用于 Client.TooManyRequestsAsync
和PoliciesTests.Test1
?问题是我无法在单元测试中通过 NullLogger.Instance
。
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;
});
}
}
您可以使用 NullLoggerFactory.Instance.CreateLogger() 创建一个 ILogger 实例。
因此您的示例将如下所示:
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
}
}
根据我的理解,您的实现看起来不错,但是您在测试用例中使用 ILogger logger 时遇到了问题。如果这种理解是正确的,那么您可以只使用 Mock(使用 Moq;)库。
然后模拟 logger.LogTrace 方法。
大多数情况下这会解决您的问题var logger = new Mock<ILogger>();
Assuming you have installed moq library and implemented in the class
// 使用最小起订量;
[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));
}
如何将 ILogger<T>
传递给静态 class Policies
及其静态方法 RateLimit(...)
,因此它适用于 Client.TooManyRequestsAsync
和PoliciesTests.Test1
?问题是我无法在单元测试中通过 NullLogger.Instance
。
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;
});
}
}
您可以使用 NullLoggerFactory.Instance.CreateLogger() 创建一个 ILogger 实例。
因此您的示例将如下所示:
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
}
}
根据我的理解,您的实现看起来不错,但是您在测试用例中使用 ILogger logger 时遇到了问题。如果这种理解是正确的,那么您可以只使用 Mock(使用 Moq;)库。
然后模拟 logger.LogTrace 方法。
大多数情况下这会解决您的问题var logger = new Mock<ILogger>();
Assuming you have installed moq library and implemented in the class
// 使用最小起订量;
[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));
}