3

I have code which adds a named HttpClient to the ServiceCollection and adds a DelegatingHandler to the client. Example code:

public class MyDelegatingHandler : DelegatingHandler
{
    // ...
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHttpClient("MyClient")
                .AddHttpMessageHandler(provider => new MyDelegatingHandler());
        });

When writing unit tests, how can I verify that the DelegatingHandler implementation added was MyDelegatingHanlder?

devklick
  • 2,000
  • 3
  • 30
  • 47

1 Answers1

2

This information is stored down in a private field of the HttpClient. You can get at it, but it's a tad unorthodox. So, you may or may not be interested, but I am going to throw this out there in case you are:

public static class HttpClientExtensions
{
    public static bool ContainsDelegatingHandler<TDelegatingHandler>(
        this HttpClient httpClient) where TDelegatingHandler: DelegatingHandler
    {
        if(httpClient == null)
        {
            throw new ArgumentNullException(nameof(httpClient));
        }

        var internalHandlerField = typeof(HttpMessageInvoker).GetField(
            "_handler", BindingFlags.NonPublic | BindingFlags.Instance);
        if(internalHandlerField == null)
        {
            throw new InvalidOperationException(
                "_handler no longer exists as a private instance field of HttpClient");
        }

        // run down the chain of delegating handlers until there are no more
        var delegatingHandler = internalHandlerField.GetValue(httpClient) as DelegatingHandler;
        while (delegatingHandler != null)
        {
            if(delegatingHandler.GetType() == typeof(TDelegatingHandler))
            {
                return true;
            }
            delegatingHandler = delegatingHandler.InnerHandler as DelegatingHandler;
        }

        return false;
    }
}

To test, you'd first have to create an HttpClient using the factory:

var httpClient = httpClientFactory.CreateClient("MyClient");

Then run it through the extension:

if(!httpClient.ContainsDelegatingHandler<MyDelegatingHandler>())
{
    throw new Exception(
        "HttpClient with name 'MyClient' does not contain " +
        $"{nameof(MyDelegatingHandler)} delegating handler.");
}
Andy
  • 12,859
  • 5
  • 41
  • 56
  • 1
    There's always a way, and you've shown me it! Thanks for your answer. Agreed, this is pretty far from best practise, but under a test context I'd say this is probably acceptable. I gave it more thought after asking the question, and I think the more suitable approach would be for me to create a `DelegatingHandlerFactory` which can be mocked, and use this to create the registering `MyDelegatingHandler` as the message handler. Thanks for your help. – devklick Sep 01 '20 at 00:07
  • @Klicker -- I was going to suggest that as well -- but sometimes folks are against adding "mock work" to their load. So this is kind of a "2 birds/1 stone" thing. Good luck! – Andy Sep 01 '20 at 00:09
  • Although after giving this _even more_ thought, I realise that adding the factory will allow me to verify that the `MyDelegatingHandler` was created, but doesnt verify that it was assigned to the HttpClient. Although perhaps not best practise, your solution gives me absolute confidence in the code example in the post. – devklick Sep 01 '20 at 00:14
  • @Klicker -- absolutely. Once you call `CreateClient`, the delegating handler will be instantiated -- but how do you check if it was instantiated? You could maybe create a static event field in your handler that could be fired from the Ctor to hit some delegate at test time, But then you are mixing in test code with production code. I don't know how else you'd do it cleanly. – Andy Sep 01 '20 at 00:19