DEV Community

Cover image for Mocking the HttpClient in .NET Core (with NSubstitute)
Lars Richter
Lars Richter

Posted on • Updated on

Mocking the HttpClient in .NET Core (with NSubstitute)

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);
Enter fullscreen mode Exit fullscreen mode

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")
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}   
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (7)

Collapse
francescoreviso profile image
FrancescoReviso

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

Collapse
n_develop profile image
Lars Richter Author

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.

Collapse
n_develop profile image
Lars Richter Author

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 little if (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.

Collapse
codemaker profile image
codemaker

Could you please post complete code :)

Collapse
n_develop profile image
Lars Richter Author

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.

Collapse
n_develop profile image
Lars Richter Author

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.

Collapse
morensya93 profile image
Faisal Morensya

This simple solution is really helpful :) Thanks!