Search code examples
c#unit-testingmoq

Mocking HttpClient in unit tests


I have some issues trying to wrap my code to be used in unit tests. The issues is this. I have the interface IHttpHandler:

public interface IHttpHandler
{
    HttpClient client { get; }
}

And the class using it, HttpHandler:

public class HttpHandler : IHttpHandler
{
    public HttpClient client
    {
        get
        {
            return new HttpClient();
        }
    }
}

And then the Connection class, which uses simpleIOC to inject the client implementation:

public class Connection
{
    private IHttpHandler _httpClient;

    public Connection(IHttpHandler httpClient)
    {
        _httpClient = httpClient;
    }
}

And then I have a unit test project which has this class:

private IHttpHandler _httpClient;

[TestMethod]
public void TestMockConnection()
{
    var client = new Connection(_httpClient);
     
    client.doSomething();  

    // Here I want to somehow create a mock instance of the http client
    // Instead of the real one. How Should I approach this?     

}

Now obviously I will have methods in the Connection class that will retrieve data (JSON) from my backend. However, I want to write unit tests for this class, and obviously I don't want to write tests against the real back end, rather a mocked one. I have tried to google a good answer to this without great success. I can and have used Moq to mock before, but never on something like HttpClient. How should I approach this problem?


Solution

  • 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