0

I have a SessionService that has HttpClient injected into it and is registered as a Typed Client See Microsoft example here.

I want to be able to write an integration test that I can control the responses made when the HttpClient is used.

I think that passing in a HttpMessageHandler to the HttpClient will allow me to intercept the request and control the response.

The problem I have is that I can't seem to add the HttpMessageHandler to the existing HttpClientFactory registration

// My client
public class SessionService
{
    private readonly HttpClient httpClient;

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

    public async Task<Session> GetAsync(string id)
    {
        var httpResponseMessage = await this.httpClient.GetAsync($"session/{id}");
        var responseJson = await httpResponseMessage.Content?.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<Session>(responseJson);
    }
}
// Live registrations
public static class HttpModule
{
    public static IServiceCollection AddHttpModule(this IServiceCollection serviceCollection) =>
        serviceCollection
            .AddHttpClient<SessionService>()
            .Services;
}
// My HttpMessageHandler which controls the response
public class FakeHttpMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK);

        // customise response

        return response;
    }
}

If I try re-register the Typed Client so I can add the HttpMessageHandler it tells me I've already registered the client and can't register it again.

public static class TestHttpModule
{
    public static IServiceCollection AddTestHttpModule(this IServiceCollection serviceCollection) =>
        serviceCollection
            .AddHttpClient<SessionService>() // <== Errors
            .AddHttpMessageHandler<FakeHttpMessageHandler>()
            .Services;
}

Any ideas?

chris31389
  • 8,414
  • 7
  • 55
  • 66
  • All the different calls to HttpClient eventually call `.Send` which is mockable. – Neil Jul 29 '20 at 10:47
  • How to you inject the mock if the HttpClient has been registered already? – chris31389 Jul 29 '20 at 11:03
  • You can mock/moq HttpClient like this: https://stackoverflow.com/questions/54227487/how-to-mock-the-new-httpclientfactory-in-net-core-2-1-using-moq although I'd question if this is suitable for integration testing. – Neil Jul 29 '20 at 11:07
  • @Neil, thanks for the response, I think that using a Mocked HttpClient would have the same issue. If I can inject a different HttpClient, then I can also inject a HttpClient with the HttpMessageHandler that I want to use. – chris31389 Jul 29 '20 at 11:13
  • 2
    *I can't seem to add the HttpMessageHandler to the existing HttpClientFactory registration* create your own factory then? – Liam Jul 29 '20 at 11:15
  • When I register a HttpClient using `.AddHttpClient()` it registers the HttpClient to an internal dictionary and I can't seem to remove it – chris31389 Jul 29 '20 at 11:31

1 Answers1

1

The problem is because when you register a HttpClient using Dependency Injection, it adds it to an internal HttpClientMappingRegistry class. The fix was to remove the registration for the registry class. This allows me to re-add the Typed client and specify a HttpMessageHandler

public static class TestHttpModule
{
    public static IServiceCollection AddTestHttpModule(this IServiceCollection serviceCollection) =>
        serviceCollection
            .AddSingleton(typeof(FakeHttpMessageHandler))
            .RemoveHttpClientRegistry()
            .AddHttpClient<SessionService>()
            .AddHttpMessageHandler<FakeHttpMessageHandler>()
            .Services;

    private static IServiceCollection RemoveHttpClientRegistry(this IServiceCollection serviceCollection)
    {
        var registryType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
            .SingleOrDefault(t => t.Name == "HttpClientMappingRegistry");
        var descriptor = serviceCollection.SingleOrDefault(x => x.ServiceType == registryType);

        if (descriptor != null)
        {
            serviceCollection.Remove(descriptor);
        }

        return serviceCollection;
    }
}
chris31389
  • 8,414
  • 7
  • 55
  • 66