Mock HttpClient using Moq
Solution 1
That particular overload method is not virtual so is unable to be overridden by Moq.
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
Which is why it throws NotSupportedException
The virtual method you are looking for is this method
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
However mocking HttpClient
is not as simple as it seems with its internal message handler.
I suggest using a concrete client with a custom message handler stub that will allow for more flexibility when faking the request.
Here is an example of a delegating handler stub.
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);
}
}
Note the default constructor is doing basically what you were trying to mock before. It also allows for more custom scenarios with a delegate for the request.
With the stub, the test can be refactored to something like
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);
}
Solution 2
Moq can mock out protected methods, such as SendAsync on the HttpMessageHandler that you can provide to HttpClient in its constructor.
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);
Copied from https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/
Solution 3
Propper mocking with HttpClient is hard work as it was written before most people did unit testing in dotnet. Sometimes I setup a stub HTTP server that returns canned responses based on pattern matching the request url, meaning you test real HTTP requests not mocks but to a localhost server. Using WireMock.net makes this really easy and runs fast enough to satisfy most of my unit testing needs.
So instead of http://some-domain.in
use a localhost server setup on some port, and then:
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'}")
);
You can find a more details and guidance on using wiremock in tests here.
Solution 4
I recently had to mock HttpClient, and I used Moq.Contrib.HttpClient. It was what I needed, and simple to use, so I thought I'd throw it out there.
Here is an example of general usage:
// 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));
For more info, check out author's blog post here.
Amitava Karan
Updated on April 07, 2021Comments
-
Amitava Karan about 3 years
I would like to unit test a class that uses
HttpClient
. We injected theHttpClient
object in the class constructor.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; } }
Now we would like to unit test the
ClassA.SendRequest
method. We are usingMs Test
for unit testing framework andMoq
for mocking.When we tried to mock the
HttpClient
, it throwsNotSupportedException
.[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))); }
How can we solve this issue?