Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap HttpClient for testability in C#

I'm calling an external API and would like my API to be unit testable. And to do that, i'm trying to wrap HttpClient. I only need one method for now. Here is my interface.

public interface IHttpClient
{
    Task<string> GetStringAsync(string url);
}

And this is how I implemented it.

public class HttpClientWrapper : IHttpClient { private readonly HttpClient _httpClient;

    public HttpClientWrapper()
    {
        // I could also inject this but I think this will be fine as is. 
        _httpClient = new HttpClient(new HttpClientHandler(), false);
    }

    public async Task<string> GetStringAsync(string url)
    {
        //validate url here
        return await _httpClient.GetStringAsync(url);
    }

}

Doubts I have? is this the right way to do it? Will setting the bool parameter result in resource leaking here? I read a couple of conflicting ideas about whether HttpClient has to be disposed on every call or not. I took, the not disposing side but am not really quite certain though.
If there is a way to use HttpClient without having a wrapper but make the API testable, that will be great too. But so far, i failed to get that working.

Thanks, CleanKoder

like image 771
user3818435 Avatar asked Jul 09 '14 00:07

user3818435


People also ask

Is HttpClient a singleton?

The HttpClient class is more suitable as a singleton for a single app domain. This means the singleton should be shared across multiple container classes. With this tactic, you do get a singleton, but this makes it difficult to share. The HttpClient class implements the IDisposable interface.

How do you mock Httpclienthandler?

1) Create a test-only overload where you can pass in a custom handler. I've used this approach with great success for years. 2) Abstract the class to an HttpClient and inject that instead. In theory your abstractions should be designed to interfaces and you can hide the fact they even use HTTP.

How does HttpClient work c#?

The HttpClient class instance acts as a session to send HTTP requests. An HttpClient instance is a collection of settings applied to all requests executed by that instance. In addition, every HttpClient instance uses its own connection pool, isolating its requests from requests executed by other HttpClient instances.

What is AddHttpClient?

AddHttpClient(IServiceCollection) Adds the IHttpClientFactory and related services to the IServiceCollection. AddHttpClient(IServiceCollection, String) Adds the IHttpClientFactory and related services to the IServiceCollection and configures a named HttpClient.


1 Answers

While it could still be nice to create an interface for the client, the HttpClient class is actually designed with testability in mind! When instantiating your HttpClient you can inject a custom HttpMessageHandler. By overriding Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in this class, you can interrupt all requests before they are actually written to the socket, inspecting them and returning whatever you see fit.

Here is an example of such a test double I wrote in a project, feel free to modify it to suit your needs:

public class FakeHttpMessageHandler : HttpMessageHandler
{
    public HttpRequestMessage LastRequest;
    public string LastRequestString = string.Empty;
    public string ResponseContent = string.Empty;
    public HttpStatusCode ResponseStatusCode = HttpStatusCode.OK;

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (request.Content != null)
        {
            LastRequestString = await request.Content.ReadAsStringAsync();
        }
        LastRequest = request;
        return await Task.FromResult(new HttpResponseMessage
        {
            StatusCode = ResponseStatusCode,
            Content = new StringContent(ResponseContent)
        });
    }
}

You could also use some isolation framework like NSubstitute if you think that's more appropriate for your project.

like image 157
sara Avatar answered Sep 29 '22 04:09

sara