The problem
I was writing a client library for a webservice. Inside of the client I used the Microsoft HttpClient
class to call the webservice. So, no magic really.
In our company, we write tests for almost all of the code that we write. That's pretty awesome on the one hand, but can be quite difficult sometimes.
I was writing unit tests, which means I didn't want to call the real service. For this reason, I needed to fake the response of the webservice. When you are working with the HttpClient
, this means mocking the returned value of the HttpClient.SendAsync()
method.
We are using .NET Core 2.2, xUnit.net and NSubstitute.
Using this setup, the code would normally look something like this:
var clientMock = Substitute.For<HttpClient>();
clientMock.SendAsync(Arg.Any<HttpRequestMessage>())
.Returns(Task.FromResult(new HttpResponseMessage
{
Content = new StringContent("Stuff I want to return"),
StatusCode = HttpStatusCode.OK
}));
var myObject = new MyObject(clientMock);
var result = myObject.DoStuff();
Assert.Equal("Some result", result.SomeProperty);
If you are familiar with NSubstitute, you might have spotted the problem already. It is the second line, that is the problem.
NSubstitute is calling the specific method and reconfiguring the returned value.
So the SendAsync
method is called with null
as the parameter. And, who would have thought it, you must not request a empty (or null) HttpRequestMessage
, because you don't have
an URL, or anything else. This means, already the setup of the mock throws an exception.
So, how do I mock the response then?
The solution
The HttpMessageHandler
comes to the rescue. It's the heart of the HttpClient
class and it does the actual handling of the requests.
You just implement a really short handwritten mock, that returns the desired response.
But why does it have to be "handwritten"? You are using a mocking framework, aren't you?
Yes, I'm using NSubstitute as my mocking framework. And NSubstitute relies on the public interface of the classes/interfaces it creates mocks for.
Sadly, the SendAsync
method of the HttpMessageHandler
is interal protected
. So NSubstitute is not able to mock it. :-(
Just use Moq. It's a great tool as well.
Yes, Moq is pretty cool as well. But because we are using NSubstitute in every project, it's not really an option to change the mocking library, if the problem can be solved with a few extra lines of code.
That's why I had to create a handwritten mock.
The simplest version of the mock would look like this:
public class MockHttpMessageHandler : HttpMessageHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("my string, that needs to be returned")
};
}
}
Of course, this now just works for a single test / a single return value. If you need to mock multiple return values, I have a simple solution.
public class MockHttpMessageHandler : HttpMessageHandler
{
private readonly string _response;
public MockHttpMessageHandler(string response)
{
_response = response;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(_response)
};
}
}
Maybe you also want to control the returned status code from inside your tests.
public class MockHttpMessageHandler : HttpMessageHandler
{
private readonly string _response;
private readonly HttpStatusCode _statusCode;
public MockHttpMessageHandler(string response, HttpStatusCode statusCode)
{
_response = response;
_statusCode = statusCode;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return new HttpResponseMessage
{
StatusCode = _statusCode,
Content = new StringContent(_response)
};
}
}
The last tweak that you might need is the ability to check the passed request object/request content and the number of times the method was invoked. Therefore I just put the Content
and the Number of calls
into a public property,
which you can use for your assertions in the test.
public class MockHttpMessageHandler : HttpMessageHandler
{
private readonly string _response;
private readonly HttpStatusCode _statusCode;
public string Input { get; private set; }
public int NumberOfCalls { get; private set; }
public MockHttpMessageHandler(string response, HttpStatusCode statusCode)
{
_response = response;
_statusCode = statusCode;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
NumberOfCalls++;
if (request.Content != null) // Could be a GET-request without a body
{
Input = await request.Content.ReadAsStringAsync();
}
return new HttpResponseMessage
{
StatusCode = _statusCode,
Content = new StringContent(_response)
};
}
}
Simple and functional.
Now you can create the HttpClient in your test and hand it an instance of you MockHttpMessageHandler into the constructor.
(My) final version
My final test(s) look like this:
[Fact]
public async Task TestMyObjectDoStuff()
{
var messageHandler = new MockHttpMessageHandler("TEST VALUE", HttpStatusCode.OK);
var httpClient = new HttpClient(messageHandler);
var sut = new MyObject(httpClient);
var result = await sut.DoStuff("maybe some input");
Assert.Equal("TEST VALUE", result.SomeProperty);
Assert.Equal("maybe some input", messageHandler.Input);
Assert.Equal(1, messageHandler.NumberOfCalls);
}
The test now checks if the messageHandler was called with the correct value and that the client wraps the response correctly.
If you want to see a full example of this, check out the following GitHub repo: https://github.com/n-develop/HttpClientMock
What do you think about this approach? Are you doing the same thing or do you have a completely different way of dealing with this problem? Let me know in the comments.
Top comments (9)
Thanks Lars, this have been really useful for me. Just a remark for a more general context, in your implementation Input = await request.Content.ReadAsStringAsync(); works for SendAsync but it will throw an exception in the case we use GetAsync, since the request will be null
Hi Francesco,
you are totally right. There is still a lot of room for improvement. But I'm happy that the post helped you. :-)
I just realized, that codemaker asked for the complete code 2 months ago. Shame on me. I will make a GitHub repo soon und I will take care for the
GetAsync
case as well.Hi Francesco,
I finally had time to create a little sample app. You can check it out on my GitHub profile under github.com/n-develop/HttpClientMock
I also checked the problem with
GetAsync
and found the reason. Of course, in a GET request, we don't have a body. So I added a littleif (request.Content != null)
in the mock class. I also updated the code in the post.Thanks again for pointing it out. Now it should work even better.
This was really helpful. Thanks Lars!
Just want to add:-
If your implementation does not accepts HttpClient in constructor and creates HttpClient directly, then you may use IHttpClientFactory.CreateClient() in your code. Now mock this IHttpClientFactory.CreateClient() to return your HttpClient in test.
Do you an example for
//Mock mockHandler = new Mock();
//mockHandler
//.Protected()
//.Setup>(
//"SendAsync",
//ItExpr.IsAny(),
//ItExpr.IsAny()
//)
//.Returns(() =>
//{
// throw new Exception("Http Client Exception Error");
//});
//return new HttpClient(mockHandler.Object);
Could you please post complete code :)
Hi codemaker,
I'm so sorry, that I missed your comment on this. I will create a public GitHub repo with the entire code and post the link in here.
So, sorry for the long delay.
You can check out a full, yet short, example here: github.com/n-develop/HttpClientMock
I hope it helps.
This simple solution is really helpful :) Thanks!