Макет 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 в тестах здесь.