ASP.NET Основной макет HttpClient с пользовательским HttpClientHandler

#c# #unit-testing #asp.net-core #asp.net-core-webapi #moq

#c# #модульное тестирование #asp.net-ядро #asp.net-core-webapi #moq

Вопрос:

У меня проблемы с издевательством Moq . Обычно, имея a HttpClient , я бы издевался над ним, вводя HttpClient в базовый класс следующим образом:

 public class MyClass
{
    private readonly HttpClient httpClient;

    public MyClass(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }
}
  

Но теперь у меня есть разные функции в моем классе MyClass , которым нужен такой пользовательский интерфейс HttpClientHandler :

 HttpClientHandler httpClientHandler = new HttpClientHandler();
...
using var client = new HttpClient(httpClientHandler);
  

Если бы я просто ввел a HttpClient в MyClassTest с var service = new MyClass(httpMock.Object); помощью, тогда HttpClient был бы перезаписан.

Каков был бы правильный способ протестировать мои функции и не выполнять реальный HTTP-вызов?

Комментарии:

1. Вы должны заглянуть в IHttpClientFactory и ввести / назвать HttpClient инъекцию.

2. 1) Создайте перегрузку только для тестирования, в которую вы можете передать пользовательский обработчик. Я использовал этот подход с большим успехом в течение многих лет. 2) Абстрагируйте класс от HttpClient и введите его вместо этого. Теоретически ваши абстракции должны быть разработаны для интерфейсов, и вы можете скрыть тот факт, что они даже используют HTTP.

3. Я пошел с public MyClass(HttpClientHandler httpClientHandler = null) , а затем вызвал его с помощью макета: var mockHttpMessageHandler = new Mock<HttpClientHandler>(); . Он выполняет свою работу, я просто не уверен, насколько это «чистая» или «лучшая практика».

Ответ №1:

Я полагаю, вы используете типизированный клиентский подход IHttpClientFactory . Вот почему ваш MyClass ctor получает HttpClient экземпляр.

Если вам нужно издеваться над этим HttpClient , я предлагаю вам последовать совету Хамида Мосаллы.

Короче говоря, есть вспомогательный класс, который делает HttpMessageHandler SendAsync его макетируемым (без необходимости использования Moq.Защищено).

 public class FakeHandler: HttpMessageHandler
{
    public virtual HttpResponseMessage Send(HttpRequestMessage request)
    {
        throw new NotImplementedException();
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(Send(request));
    }
}
  

Вы можете использовать этот вспомогательный класс, подобный этому, для HttpClient моделирования любого вызова:

 var httpResponse = new HttpResponseMessage
{
    Content = new StringContent(JsonConvert.SerializeObject(responseObject))
};

var mockHandler = new Mock<FakeHandler> { CallBase = true };
mockHandler
    .Setup(handler => handler.Send(It.IsAny<HttpRequestMessage>()))
    .Returns(httpResponse);

var mockHttpClient = new HttpClient(mockHandler.Object);
var SUT = new MyClass(mockHttpClient);
  

Комментарии:

1. Это тот же подход, который предложил @Andy. Я обязательно рассмотрю это, когда у меня будет больше времени, так как это кажется более «чистым».

Ответ №2:

Каков был бы правильный способ протестировать мои функции и не выполнять реальный HTTP-вызов?

Возможно, это не то, что вы ищете, но я предлагаю вам рассмотреть мудрость Эндрю Локка — не тестируйте контроллеры API / MVC в ASP.NET Ядро.

Для .NET Core (и .NET 5) вам следует избегать издевательства над HttpClient, если вы тестируете класс контроллера.

Если класс контроллера не является вашим SUT, я бы обернул HttpClient в интерфейс фасада и издевался над этим.

Комментарии:

1. Ну, я тестирую базовый класс обслуживания для контроллера. Например, выборка и обработка данных с другой страницы. Затем результат передается контроллеру. Сам контроллер тестируется на основе макетного интерфейса сервисного уровня. На мой взгляд, класс Service должен быть протестирован, и для этого мне нужен HttpClient -mock