HTTPClient Mocking - C#

HTTPClient Mocking - C#

What is mocking?

Mocking is a testing technique where mock objects are used instead of real objects for testing purposes. In general, in dotnet Moq or nSubstitute is a very popular mocking framework.

In this article, we will use moq.

What is our goal?

Our goal is, we need to mock the HTTP Client. If you have ever done unit testing in dotnet and tried to test the HTTPClient in dotnet you already know how tricky is to test the HttpClient instance in dotnet. So no further talking let's do it.

Why this is even a problem?

Let's first understand why this problem even exists. Usually, in any mocking framework, you can set up the desired method and set up the return type (what do you want to return in general). But in terms of HTTPClient, this isn’t the case.

Let's mock it in the usual way (using moq) and see what happens.

The Usual Way (using MOQ)

Let's prepare our use case by creating a service class name StockHttpService and injecting HTTPClient as well as calling any method to get the data. In our case, we are calling GetStringAsync() method.

Let's first create a unified interface first to define our services.

public interface IService
{
    ValueTask<string> Run();
}
public class StockHttpService : IService
{
    private readonly HttpClient _httpClient;

    public StockHttpClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async ValueTask<string> Run()
    {
        var response = await _httpClient.GetStringAsync("https://pritompurkayasta.me");
        if (response == null)
            return string.Empty;    

        return response;
    }
}

We will create a test class called StockHttpServiceTest and mock HttpClient and call StockHttpService to get things done.

public class StockHttpServiceTest
{
    private readonly StockHttpService _sut;
    private readonly Mock<HttpClient> _httpClientMock;

    public StockHttpClientTest()
    {
        _httpClientMock = new Mock<HttpClient>();
        _sut = new StockHttpClient(_httpClientMock.Object);
    }


    [Fact]
    public async Task GetTest()
    {
        _httpClientMock.Setup(x => x.GetStringAsync("https://pritompurkayasta.me")).Returns(Task.FromResult("hello world"));
        var response = await _sut.Run();
        Assert.Equal("hello world", response);
    }
}

Fantastic πŸŽ‰. Now we will run the test case and see if the output matches or not.

1.png

Ahhhh. πŸ™„πŸ˜ It is throwing an exception stating this method cannot be overridden. In a general way of saying it, the creator left it, not to be mocked. Damn. So what’s the solution then?

There are effectively two approaches that can solve this problem.

  1. Create abstraction.
  2. Create a fake HTTP Handler.

Let's go through each of these steps.

Abstraction:

As we have seen that the Stock HTTPClient class cannot be overidden so we can create an abstraction out of the HTTPClient itself so that we can mock that abstraction, not the HTTPClient itself.

Let's visualize it by creating our own HttpClient abstraction. We will create an CustomHttpClient and create the GetStringAsync() method to fulfill our need.

internal interface IHttpClient
{
    ValueTask<string> GetStringAsync(string url);
}

internal class CustomHttpClient : IHttpClient
{
    private readonly HttpClient httpClient;

    public CustomHttpClient(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    public async ValueTask<string> GetStringAsync(string url) => await this.httpClient.GetStringAsync(url);
}

Great. Let's consume this by creating our Service class naming CustomHttpService and injecting the IHttpClient interface that we just created.

internal class CustomHttpService : IService
{
    private readonly IHttpClient _httpClient;

    public CustomHttpService(IHttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async ValueTask<string> Run()
    {
        var response = await _httpClient.GetStringAsync("https://pritompurkayasta.me");
        if (response == null)
            return string.Empty;
        return response;
    }
}

Nothing changes on the consumer side apart from the type. Let's write some tests then. We will create a test class called CustomHttpServiceTest and mock our own abstracted interface IHttpClient.

public class CustomHttpServiceTest
{
    private CustomHttpService _sut;
    private Mock<IHttpClient> _httpClientMock;

    public CustomHttpServiceTest()
    {
        _httpClientMock = new Mock<IHttpClient>();
        _sut = new CustomHttpService(_httpClientMock.Object);
    }


    [Fact]
    public async Task CustomTesting()
    {
        _httpClientMock.Setup(x => x.GetStringAsync("https://pritompurkayasta.me")).Returns(ValueTask.FromResult("hello world"));
        var response = await _sut.Run();
        Assert.Equal("hello world", response);
    }
}

And the result is 2.png Horray it works πŸͺ‚

But this approach requires a headache of giving the definition of required HTTP client methods. Other than that we are good to go with the approach.

Fake HTTP Handler:

In this approach, we don’t need to change anything to our StockHttpService (consumer end / consumer end that can be console app or web api). We just need to feed the custom HttpMessageHandler instance into our test method to test it.

Let's understand what is HttpMessageHandler

A Message Handler is a class that receives an HTTP request and returns an HTTP response. The base class of a Message Handler is HttpMessageHandler.

Lets create our custom handler then. We will create a class called CustomHttpMessageHandler that will inherit the abstract class HttpMessageHandler. We will change the functionality a little to adjust our needs.

public class CustomHttpMessageHandler : HttpMessageHandler
{
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _callBackFunction;

    public CustomHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callBackFunction)
    {
        _callBackFunction = callBackFunction;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return await _callBackFunction(request, cancellationToken);
    }
}

We have added a delegate to have control over what the sendAsync() method will return. We will see that below.

We are ready to write our test class. Let's create a class called StockHttpServiceWithHandlerTest and do the following.

internal class StockHttpServiceWithHandlerTest
{
    private readonly CustomHttpMessageHandler _customHttpMessageHandler;
    public StockHttpServiceWithHandlerTest()
    {
        _customHttpMessageHandler = new CustomHttpMessageHandler((request, token) =>
        {
            var response = new HttpResponseMessage()
            {
                Content = new StringContent("Hello World"),
                StatusCode = System.Net.HttpStatusCode.OK
            };
            return Task.FromResult(response);
        });

    }
}

We are setting the return value from our CustomHttpMessageHandler class through the constructor. Remember we have added a Func Delegate to our class? This is the purpose of creating that delegate.

Let's finish the test case then.

public class StockHttpServiceWithHandlerTest
{
    private CustomHttpMessageHandler _customHttpMessageHandler;
    private HttpClient _httpClient;
    private StockHttpService _sut;

    public StockHttpServiceWithHandlerTest()
    {
        _customHttpMessageHandler = new CustomHttpMessageHandler((request, token) =>
        {
            var response = new HttpResponseMessage()
            {
                Content = new StringContent("Hello World"),
                StatusCode = System.Net.HttpStatusCode.OK
            };
            return Task.FromResult(response);
        });
        _httpClient = new HttpClient(_customHttpMessageHandler);
        _sut = new StockHttpService(_httpClient);
    }

    [Fact]
    public async Task Should_ReturnTheResponseContent()
    {
        var response = await this._sut.Run();
        Assert.Equal("Hello World", response);
    }
}

and the moment of truth

3.png

We can modify the handler to satisfy what ever we want. We can even modify how the handler instantiated. I have made this as easy as possible for understanding purpose.

Share any ideas if you have done something other than what I have shown. That’s it. Hope it helped πŸ™