Search code examples
c#asp.netdependency-injectiondotnet-httpclient

Inject HttpClient from WebApplicationFactory


I'm trying to create a (mostly) unified set of integation tests that can either be targetted at an in-memory API created from a WebApplicationFactory or at a fully-deployed version of our app. Using XUnit.DependencyInjection, I'm planning on injecting a HttpClient into my tests that either points to the test server or the real app based on an environment variable.

So to create a client for the test server, I can just run the following in Startup.cs:

WebApplicationFactory<Program> app = new();
HttpClient client = app.CreateClient();

This seems to work. However, I have absolutely no idea how to inject this implementation for the HttpClient into the individual test classes.

Something like this, doesn't work (such an overload doesn't exist):

services.AddHttpClient<MyTestClass>(client);

And neither does this (the injected client has the BaseAddress set to null for some reason):

services.AddHttpClient<InMemoryServerSelfTests>(c =>
                                    {
                                        c.BaseAddress           = client.BaseAddress;
                                        c.Timeout               = client.Timeout;
                                    });

My only other thought is to create a new class that wraps both clients and inject that instead but that seems messy:

public class TestClientWrapper
{
    public readonly HttpClient Client;
    public TestClientWrapper(InMemoryTestServer server)
    {
        Client = server.CreateClient();
    }

    public TestClientWrapper(HttpClient client)
    {
        Client = client;
    }
}

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    string targetEndpoint = Environment.GetEnvironmentVariable("targetEndpoint"); // Make this configurable
    bool   isLocal        = string.IsNullOrEmpty(targetEndpoint);
    
    if (isLocal)
    {
        InMemoryTestServer app = new();
        services.AddSingleton(new TestClientWrapper(app));
    }
    else
    {
        HttpClient client = new();
        services.AddSingleton(new TestClientWrapper(client));
    }
}

So really, I'm a bit stumped... Any ideas on how to accomplish this?


Solution

  • The problem is that the HttpClient generated by the WebApplicationFactory is special as the WebApplicationFactory is hosted in memory and is not visible out of process (I think that's what I read elsewhere). What that means is that copying over the settings doesn't work.

    The only way I've managed to get the WebApplicationFactory client registered so that it is resolvable is to register an instance of IHttpClientFactory with the container that returns clients from the WebApplicationFactory.

    public class TestHttpClientFactory<TStartup> : IHttpClientFactory 
        where TStartup : class
    {
        private readonly WebApplicationFactory<TStartup> _appFactory;
    
        public TestHttpClientFactory(WebApplicationFactory<TStartup> appFactory) => _appFactory = appFactory;
    
        public HttpClient CreateClient(string name) => _appFactory.CreateClient();
    }
    
    services.AddSingleton<IHttpClientFactory>(new TestClientFactory(...));
    

    Something along those lines will work.