Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create default HttpClientFactory for integration test

I have a typed client which I want to register as a singleton:

public class SomeHttpClient
{
    private readonly IHttpClientFactory _clientFactory;

    public SomeHttpClient(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public Task MakeSomeCallAsync(...)
    {
        var client = _clientFactory.Create();
        // Do more stuff
    }
}

The typed client must be a singleton, because it has some circuit breaker logic which breaks the circuit if the API is rate limiting the client. The circuit must be broken across the entire application and obviously not just the current request pipeline (otherwise the client is going to keep hitting the API which is already rate limiting from other instances). This is already implemented by using a typed client which encapsulates that functionality (not Polly though).

Now because the typed client is going to be a singleton we can't inject a single HttpClient because that would bring all the problems we had before with a single long living HttpClient. The obvious solution is to inject a HttpClientFactory which now can be used to issue a HttpClient instance every time the typed client needs one without having to worry about lifetime management etc, since that has been deferred to the HttpClientFactory now.

My question now is how can I create a default HttpClientFactory for a simple functional integration test where I want to instantiate a typed client and see if I can hit the API and get a successful response from an automated test?

I don't want to set up an ASP.NET Core TestServer and a whole application around it because that's an overkill for the sort of functional testing I want to do as this point.

There is no public constructor which I can use for injecting a HttpClientFactory?

like image 288
dustinmoris Avatar asked Sep 30 '18 09:09

dustinmoris


3 Answers

Here's an implementation that will return a new HttpClient instance for each call (just as the interface contract specifies), but re-uses the same HttpMessageHandler:

public sealed class DefaultHttpClientFactory : IHttpClientFactory, IDisposable
{
    private readonly Lazy<HttpMessageHandler> _handlerLazy = new (() => new HttpClientHandler());

    public HttpClient CreateClient(string name) => new (_handlerLazy.Value, disposeHandler: false);

    public void Dispose()
    {
        if (_handlerLazy.IsValueCreated)
        {
            _handlerLazy.Value.Dispose();
        }
    }
}

like image 63
marsze Avatar answered Nov 15 '22 07:11

marsze


My question now is how can I create a default HttpClientFactory for a simple functional integration test where I want to instantiate a typed client and see if I can hit the API and get a successful response from an automated test?

Your test can take the exact code that you use to configure the HttpClientFactory in StartUp (this is an example using a Polly policy, but it could be any configuration on HttpClientFactory).

IServiceCollection services = new ServiceCollection(); // [1]

services.AddHttpClient(TestClient)
    .AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError()
    .RetryAsync(3); // [2]

Line [2] could be the exact code from your StartUp class. You could factor it out of the normal ConfigureServices(...) method to a separate static method which your test can also call - so your test tests exactly what StartUp configures.

Then in the test just:

IHttpClientFactory factory = services
    .BuildServiceProvider()
    .GetRequiredService<IHttpClientFactory>();

var sut = new SomeHttpClient(factory);
like image 33
mountain traveller Avatar answered Nov 15 '22 07:11

mountain traveller


I just create one myself for testing (I thought there's perhaps already a method that could do that for me):

type DefaultHttpClientFactory() =
    interface IHttpClientFactory with
        member __.CreateClient (name) =
            new HttpClient()
like image 5
dustinmoris Avatar answered Nov 15 '22 07:11

dustinmoris