У меня проблемы с выполнением модульного тестирования при использовании Polly
и HttpClient
.
В частности, Polly и HttpClient используются для ASP.NET Core Web API Controller по ссылкам ниже:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory
Проблемы (1 и 2) указаны внизу.Интересно, правильный ли это способ использования Polly
и HttpClient
.
ConfigureServices
Настройка политики Polly и выбор настраиваемого HttpClient, CustomHttpClient
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddHttpClient<HttpClientService>()
.AddPolicyHandler((service, request) =>
HttpPolicyExtensions.HandleTransientHttpError()
.WaitAndRetryAsync(3,
retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))
);
}
CarController
CarController зависит от HttpClientService
, который внедряется платформой автоматически без явной регистрации.
Обратите внимание, что HttpClientService
вызывает проблемы с модульным тестом, так как Moq
не может смоделировать не виртуальный метод, который будет упомянут позже.
[ApiVersion("1")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class CarController : ControllerBase
{
private readonly ILog _logger;
private readonly HttpClientService _httpClientService;
private readonly IOptions<Config> _config;
public CarController(ILog logger, HttpClientService httpClientService, IOptions<Config> config)
{
_logger = logger;
_httpClientService = httpClientService;
_config = config;
}
[HttpPost]
public async Task<ActionResult> Post()
{
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
string body = reader.ReadToEnd();
var statusCode = await _httpClientService.PostAsync(
"url",
new Dictionary<string, string>
{
{"headerID", "Id"}
},
body);
return StatusCode((int)statusCode);
}
}
}
HttpClientService
Похоже на CarController
, HttpClientService
проблема с модульным тестом, потому что PostAsync HttpClient
не может быть смоделирован.HttpClient
внедряется платформой автоматически без явной регистрации.
public class HttpClientService
{
private readonly HttpClient _client;
public HttpClientService(HttpClient client)
{
_client = client;
}
public async Task<HttpStatusCode> PostAsync(string url, Dictionary<string, string> headers, string body)
{
using (var content = new StringContent(body, Encoding.UTF8, "application/json"))
{
foreach (var keyValue in headers)
{
content.Headers.Add(keyValue.Key, keyValue.Value);
}
var response = await _client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
return response.StatusCode;
}
}
Задача 1
Модульный тест: Moq
не может смоделировать HttpClientService
PostAsync
метод.Я МОГУ изменить его на виртуальный, но мне интересно, если это лучший вариант.
public class CarControllerTests
{
private readonly Mock<ILog> _logMock;
private readonly Mock<HttpClient> _httpClientMock;
private readonly Mock<HttpClientService> _httpClientServiceMock;
private readonly Mock<IOptions<Config>> _optionMock;
private readonly CarController _sut;
public CarControllerTests() //runs for each test method
{
_logMock = new Mock<ILog>();
_httpClientMock = new Mock<HttpClient>();
_httpClientServiceMock = new Mock<HttpClientService>(_httpClientMock.Object);
_optionMock = new Mock<IOptions<Config>>();
_sut = new CarController(_logMock.Object, _httpClientServiceMock.Object, _optionMock.Object);
}
[Fact]
public void Post_Returns200()
{
//System.NotSupportedException : Invalid setup on a non-virtual (overridable in VB) member
_httpClientServiceMock.Setup(hc => hc.PostAsync(It.IsAny<string>(),
It.IsAny<Dictionary<string, string>>(),
It.IsAny<string>()))
.Returns(Task.FromResult(HttpStatusCode.OK));
}
}
}
Задача 2
Юнит-тест: аналогично HttpClientService
, Moq
не может смоделировать HttpClient
метод PostAsync
.
public class HttpClientServiceTests
{
[Fact]
public void Post_Returns200()
{
var httpClientMock = new Mock<HttpClient>();
//System.NotSupportedException : Invalid setup on a non-virtual (overridable in VB) member
httpClientMock.Setup(hc => hc.PostAsync("", It.IsAny<HttpContent>()))
.Returns(Task.FromResult(new HttpResponseMessage()));
}
}
ASP.NET Core API 2.2
Обновление
Исправленоопечатка CustomHttpClient для HttpClientService
Обновление 2
Решение для проблемы 2