Mocking Httpclient in Unit Tests

Mocking HttpClient in unit tests

Your interface exposes the concrete HttpClient class, therefore any classes that use this interface are tied to it, this means that it cannot be mocked.

HttpClient does not inherit from any interface so you will have to write your own. I suggest a decorator-like pattern:

public interface IHttpHandler
{
HttpResponseMessage Get(string url);
HttpResponseMessage Post(string url, HttpContent content);
Task<HttpResponseMessage> GetAsync(string url);
Task<HttpResponseMessage> PostAsync(string url, HttpContent content);
}

And your class will look like this:

public class HttpClientHandler : IHttpHandler
{
private HttpClient _client = new HttpClient();

public HttpResponseMessage Get(string url)
{
return GetAsync(url).Result;
}

public HttpResponseMessage Post(string url, HttpContent content)
{
return PostAsync(url, content).Result;
}

public async Task<HttpResponseMessage> GetAsync(string url)
{
return await _client.GetAsync(url);
}

public async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)
{
return await _client.PostAsync(url, content);
}
}

The point in all of this is that HttpClientHandler creates its own HttpClient, you could then of course create multiple classes that implement IHttpHandler in different ways.

The main issue with this approach is that you are effectively writing a class that just calls methods in another class, however you could create a class that inherits from HttpClient (See Nkosi's example, it's a much better approach than mine). Life would be much easier if HttpClient had an interface that you could mock, unfortunately it does not.

This example is not the golden ticket however. IHttpHandler still relies on HttpResponseMessage, which belongs to System.Net.Http namespace, therefore if you do need other implementations other than HttpClient, you will have to perform some kind of mapping to convert their responses into HttpResponseMessage objects. This of course is only a problem if you need to use multiple implementations of IHttpHandler but it doesn't look like you do so it's not the end of the world, but it's something to think about.

Anyway, you can simply mock IHttpHandler without having to worry about the concrete HttpClient class as it has been abstracted away.

I recommend testing the non-async methods, as these still call the asynchronous methods but without the hassle of having to worry about unit testing asynchronous methods, see here

Mock HttpClient using Moq

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);
}

Mocking HttpClient in xunit for different endpoint

I think that you should set up two SendAsync calls as in the following.

// response to /endpoint1
var responseMessage1 = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("test_return")
};

// response to /endpoint2
var responseMessage2 = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("test_other_return")
};

var mock = new Mock<HttpMessageHandler>();


// mock a call to /endpoint1
mock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(
m => m.RequestUri.AbsolutePath.Contains(
"endpoint1")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(responseMessage1);

// mock a call to /endpoint2
mock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(
m => m.RequestUri.AbsolutePath.Contains(
"endpoint2")),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(responseMessage2);

var client = new HttpClient(mock.Object);

var agent = new Agent(client);
var resp = agent.Run();

Mocking httpclient multiple times in a single Unit Test

Sometime setting up a mock may not always be the best approach. Since HttpClient really depends on HttpMessageHandler you can create a simple one to handle the expected requests

class TestMessageHandler : HttpMessageHandler {
private readonly IDictionary<string, HttpResponseMessage> messages;

public TestMessageHandler(IDictionary<string, HttpResponseMessage> messages) {
this.messages = messages;
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var response = new HttpResponseMessage(HttpStatusCode.NotFound);
if (messages.ContainsKey(request.RequestUri.ToString()))
response = messages[request.RequestUri.ToString()] ?? new HttpResponseMessage(HttpStatusCode.NoContent);
response.RequestMessage = request;
return Task.FromResult(response);
}
}

The above handler just uses the URL of the incoming request to find the response in the dictionary. It can be refactored to handle more complex requests if needed, but for a simple request this will serve just fine.

Pass the handler to the client created for the test

public async Task MyTestMethod() {
//Arrange
Dictionary<string, HttpResponseMessage> messages = new Dictionary<string, HttpResponseMessage>();
messages.Add("https://somesite1.com/ping", new HttpResponseMessage() {
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':'0001'," +
"'name':'Jonah'," +
"'street':'Cambridge Street 1234'}]"
)
});
messages.Add("https://somesite2.com/ping", new HttpResponseMessage() {
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':'34'," +
"'insurance':'Etx'," +
"'insider':'daily'," +
"'collectives':'4'}]"
)
});

var client = new HttpClient(new TestMessageHandler(messages));

//...inject client as needed

The client with the custom handler can now be invoked as needed when verifying the expected behavior of the subject under test.



Related Topics



Leave a reply



Submit