Модульное тестирование с помощью ILoggerProvider - PullRequest
2 голосов
/ 08 мая 2020

У меня есть такой код в моей настройке модульного теста:

IServiceCollection services = new ServiceCollection();
services.AddSingleton<IDependency, Dependency>();
services.AddLogging();
services.RemoveAll<ILoggerProvider>();
services.AddSingleton<ILoggerProvider, MyCustomProvider>(provider =>
{
    var myDependency = provider.GetService<IDependency>();
    return new MyCustomProvider(myDependency );
});
var serviceProvider = services.BuildServiceProvider();

var logger = serviceProvider.GetService<ILogger>();

Когда я запускаю это logger имеет значение null.

Лямбда для создания фабрики DI MyCustomProvider никогда не называется. Ни конструктор MyCustomProvider, ни конструктор созданного мной класса, который реализует ILogger.

Я предполагаю, что мне не хватает шага, чтобы связать это. Но существует много противоречивой документации между. Net Core 1, 2 и 3.

Что мне нужно сделать, чтобы подключить моего провайдера к. Net Core 3 way ? (Так что я могу получить ILogger, который его использует.)

ПРИМЕЧАНИЯ:

  • Я не звоню LoggingBuilder.AddProvider. Но если вы посмотрите на исходный код для этого расширения, он просто вызовет AddSingleton, что я и делаю, но с фабрикой. (Понятно, что здесь .)
  • Точно так же services.RemoveAll<ILoggerProvider>() то же самое, что и вызов ClearProviders.
  • Моя цель - смоделировать инъекцию ILogger в конструктор класса.

Например:

public class SomeClass
{
     public SomeClass(ILogger<SomeClass> logger)
     {
          // Do Stuff
     }
}

Как-то это делается без указания провайдера. Я бы хотел, чтобы в моем модульном тесте было так же. (Просто используйте моего собственного провайдера.)

Ответы [ 3 ]

2 голосов
/ 09 мая 2020

Причина, по которой ваша переменная logger равна null в примере , состоит в том, что ILogger фактически не зарегистрирован как служба. Если вы посмотрите на источник AddLogging, вы увидите, что это только регистры ILogger<> и ILoggerFactory. Если вы когда-либо пытались принять ILogger вместо ILogger<MyClass> через. NET Core DI, вы столкнетесь со следующим исключением *:

System.InvalidOperationException: ' Невозможно разрешить службу для типа «Microsoft.Extensions.Logging.ILogger» при попытке активировать «Your.Service»

Исходя из этого, ваш код тестирования ошибочен, поскольку вы никогда бы не получил ILogger во-первых. Чтобы убедиться, что ваш код на самом деле работает , измените свой тестовый код так, чтобы вместо него он получал ILogger<SomeClass>. Результат вашей переменной будет отличным от нуля, и будет достигнута точка останова, установленная в конструкторе вашего провайдера:

// Get*Required*Service won't throw
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();

Если вы хотите, чтобы имел возможность вводить ILogger, вам нужно будет зарегистрировать его отдельно с именем категории по умолчанию **:

services.AddSingleton<ILoggerProvider, MyCustomProvider>(); // no need for lambda
services.AddSingleton<ILogger>(sp => 
  sp.GetService<ILoggerFactory>().CreateLogger("Default")
);

Теперь оба будут работать и с использованием вашего настраиваемого поставщика:

var loggerA = serviceProvider.GetRequiredService<ILogger<Program>>();
var loggerB = serviceProvider.GetRequiredService<ILogger>();

У меня есть собственный настраиваемый поставщик / фабрика / регистратор, который я использую для внедрения xUnit's ITestOutputHelper в настраиваемый регистратор, следуя тому же шаблону регистрации, что и вы, поэтому я знаю из личного опыта, что это работает. Но я также проверил, что ваш конкретный код c работает (имитирует мой собственный IDependency) - приведенный выше код выполняется, и точки останова, установленные в конструкторе MyCustomProvider, и регистрация службы срабатывают. Вдобавок, если я ввожу регистратор в класс, он тоже попадает (как и ожидалось).

Ваш комментарий «Каким-то образом это делается без указания какого-либо провайдера» дезинформирован, потому что у вас действительно есть зарегистрированный провайдер! Но даже в сценарии, когда все поставщики были очищены, а новые не добавлены, вы все равно получите ненулевой регистратор. Это потому, что LoggerFactory просто перебирает каждого провайдера, чтобы создать вокруг них новую оболочку . Если провайдеров нет, то вы, по сути, получаете регистратор без операций.

* Это досадно, поскольку некоторые инструменты (например, R #) предлагают преобразовать параметр в базовый тип ILogger, который затем нарушает DI!

** Требуется имя категории по умолчанию, поскольку нет аргумента generi c type для передачи в ILoggerFactory.CreateLogger. Для generi c ILogger<T> имя категории всегда является вариацией имени T, но мы, очевидно, не получаем этого с не-generi c версией. Если бы мне пришлось угадать , вероятно, поэтому они не регистрируют реализацию ILogger по умолчанию.

0 голосов
/ 08 мая 2020

Это может быть выстрел в темноте, но вы можете попробовать добавить свой ILoggerProvider в ILoggerFactory:

var loggerFactory = serviceProvider.GetService<ILoggerFactory();
var loggerProvider = serviceProvider.GetService<ILoggerProvider>();
loggerFactory.AddProvider(loggerProvider);

В случае, если он был построен раньше, чем вы зарегистрировали своего провайдера, он может не знать о его существовании.


В качестве альтернативы, будет ли это работать, если вы также укажете это, как решить ILogger?:

...
services.AddSingleton<ILoggerProvider, MyCustomProvider>(provider =>
{
    var myDependency = provider.GetService<IDependency>();
    return new MyCustomProvider(myDependency );
});
services.AddSingleton(typeof(ILogger<>), provider => 
{
    var loggerProvider = provider.GetService<ILoggerProvider>();
    return loggerProvider.CreateLogger("TestLogger");
});
...

Наконец ... есть ли причина, по которой вы не можете просто ввести ILoggerProvider в свои тесты и позвонить loggerProvider.CreateLogger(<test_class_name>), чтобы получить регистратор?

0 голосов
/ 08 мая 2020

NET CORE Testing Я делаю так:

в конструкторе тестового класса (или там, где вы выполняете отображение DI)

public class UnitTest1
    {

        public IConfigurationRoot Configuration { get; set; }
        private readonly IDisponibilitaService _disponibilitaService;

        public UnitTest1()
        {
            var services = new ServiceCollection();

            Configuration = new ConfigurationBuilder()
                .SetBasePath(Path.Combine(Path.DirectorySeparatorChar.ToString(), "directory", "Kalliope_CTI", "backend", "KalliopeCTITestProject"))
                .AddJsonFile("testconfig.json", optional: false, reloadOnChange: true)
                .Build();



            services.AddSingleton<ILoggerFactory, NullLoggerFactory>(); //< -- HERE TRY TO SET NullLoggerFactory


            var serviceProvider = services
                .AddOptions()
                .BuildServiceProvider();




        }


        //// your test methods
    }

Надеюсь, это поможет вам !!

...