Убедитесь, что мой обработчик делегирования был добавлен в IHttpClientBuilder

#c# #.net-core #dependency-injection #delegatinghandler

#c# #.net-ядро #внедрение зависимостей #обработчик делегирования

Вопрос:

У меня есть код, который добавляет имя HttpClient к ServiceCollection и добавляет DelegatingHandler к клиенту. Пример кода:

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

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

Как я могу при написании модульных тестов убедиться, что DelegatingHandler добавленная реализация была MyDelegatingHanlder ?

Ответ №1:

Эта информация хранится в закрытом поле HttpClient . Вы можете добраться до этого, но это немного неортодоксально. Итак, вам может быть интересно, а может и нет, но я собираюсь опубликовать это на случай, если вы:

 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;
    }
}
  

Для тестирования вам сначала нужно создать HttpClient с использованием factory:

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

Затем запустите его через расширение:

 if(!httpClient.ContainsDelegatingHandler<MyDelegatingHandler>())
{
    throw new Exception(
        "HttpClient with name 'MyClient' does not contain "  
        $"{nameof(MyDelegatingHandler)} delegating handler.");
}
  

Комментарии:

1. Всегда есть способ, и вы мне его показали! Спасибо за ваш ответ. Согласен, это довольно далеко от наилучшей практики, но в контексте тестирования я бы сказал, что это, вероятно, приемлемо. Я больше подумал об этом после того, как задал вопрос, и я думаю, что более подходящим подходом для меня было бы создать DelegatingHandlerFactory который можно подделать, и использовать это для создания регистрации MyDelegatingHandler в качестве обработчика сообщений. Спасибо за вашу помощь.

2. @Klicker — я тоже собирался предложить это — но иногда люди против добавления «макетной работы» к своей загрузке. Так что это своего рода «2 зайца / 1 камень». Удачи!

3. Хотя, еще больше подумав об этом, я понимаю, что добавление фабрики позволит мне убедиться, что MyDelegatingHandler был создан, но не проверяет, был ли он назначен HttpClient. Хотя, возможно, это не лучшая практика, ваше решение дает мне абсолютную уверенность в примере кода в сообщении.

4. @Klicker — абсолютно. После вызова CreateClient будет создан экземпляр делегирующего обработчика — но как вы проверяете, был ли он создан? Возможно, вы могли бы создать в своем обработчике поле статического события, которое могло бы запускаться из Ctor для вызова какого-либо делегата во время тестирования, но затем вы смешиваете тестовый код с производственным кодом. Я не знаю, как еще вы могли бы сделать это чисто.