Макет HttpClient с использованием Moq

Мне нравится модульное тестирование класса, который использует HttpClient, Мы ввели HttpClient Объект в конструкторе класса.

public class ClassA : IClassA
{
    private readonly HttpClient _httpClient;

    public ClassA(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<HttpResponseMessage> SendRequest(SomeObject someObject)
    {
        //Do some stuff

        var request = new HttpRequestMessage(HttpMethod.Post, "http://some-domain.in");

        //Build the request

        var response = await _httpClient.SendAsync(request);

        return response;
    }
}

Теперь нам нравится тестировать ClassA.SendRequest метод. Мы используем Ms Test для модульного тестирования инфраструктуры и Moq для насмешек.

Когда мы пытались издеваться над HttpClientкидает NotSupportedException,

[TestMethod]
public async Task SendRequestAsync_Test()
{
    var mockHttpClient = new Mock<HttpClient>();

    mockHttpClient.Setup(
        m => m.SendAsync(It.IsAny<HttpRequestMessage>()))
    .Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
}

Как мы можем решить эту проблему?

4 ответа

Решение

Этот конкретный метод перегрузки не является виртуальным, поэтому Moq не может его переопределить.

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);

Вот почему это бросает NotSupportedException

Виртуальный метод, который вы ищете, этот метод

public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);

Однако издевательство HttpClient не так просто, как кажется с его внутренним обработчиком сообщений.

Я предлагаю использовать конкретный клиент с пользовательской заглушкой обработчика сообщений, которая обеспечит большую гибкость при фальсификации запроса.

Вот пример делегирующей заглушки обработчика.

public class DelegatingHandlerStub : DelegatingHandler {
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
    public DelegatingHandlerStub() {
        _handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
    }

    public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
        _handlerFunc = handlerFunc;
    }

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

Обратите внимание, что конструктор по умолчанию делает в основном то, что вы пытались смоделировать раньше. Это также позволяет использовать более нестандартные сценарии с делегатом для запроса.

С заглушкой, тест может быть реорганизован в нечто вроде

public async Task _SendRequestAsync_Test() {
    //Arrange           
    var handlerStub = new DelegatingHandlerStub();
    var client = new HttpClient(handlerStub);
    var sut = new ClassA(client);
    var obj = new SomeObject() {
        //Populate
    };

    //Act
    var response = await sut.SendRequest(obj);

    //Assert
    Assert.IsNotNull(response);
    Assert.IsTrue(response.IsSuccessStatusCode);
}

Moq может имитировать защищенные методы, такие как SendAsync в HttpMessageHandler, которые вы можете предоставить HttpClient в его конструкторе.

var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(new HttpResponseMessage
    {
        StatusCode = HttpStatusCode.OK
     });

var client = new HttpClient(mockHttpMessageHandler.Object);

Скопировано с https://thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/

Недавно мне пришлось издеваться над HttpClient, и я использовал Moq.Contrib.HttpClient . Это было то, что мне было нужно, и простое в использовании, поэтому я решил выбросить его там.

Вот пример общего использования:

      // All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();

// A simple example that returns 404 for any request
handler.SetupAnyRequest()
    .ReturnsResponse(HttpStatusCode.NotFound);

// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
    .ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");

// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
    .ReturnsResponse(bytes, configure: response =>
    {
        response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
    })
    .Verifiable(); // Naturally we can use Moq methods as well

// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));

Для получения дополнительной информации ознакомьтесь с публикацией в блоге автора здесь .

Надлежащее копирование с помощью HttpClient - тяжелая работа, так как она была написана до того, как большинство людей провело модульное тестирование в dotnet. Иногда я настраиваю тупой HTTP-сервер, который возвращает готовые ответы на основе шаблона, соответствующего URL-адресу запроса, то есть вы тестируете реальные HTTP-запросы не на фиктивные, а на локальный сервер. Использование WireMock.net делает это действительно простым и работает достаточно быстро, чтобы удовлетворить большинство моих потребностей в модульном тестировании.

Так что вместо http://some-domain.in используйте настройки сервера localhost на каком-либо порту, а затем:

var server = FluentMockServer.Start(/*server and port can be setup here*/);
server.Given(
      Request.Create()
      .WithPath("/").UsingPost()
   )
   .RespondWith(
       Response.Create()
       .WithStatusCode(200)
       .WithHeader("Content-Type", "application/json")
       .WithBody("{'attr':'value'}")
   );

Вы можете найти более подробную информацию и руководство по использованию wiremock в тестах здесь.

Другие вопросы по тегам