使用ILoggerProvider进行单元测试

时间:2020-05-08 08:19:37

标签: c# asp.net-core .net-core asp.net-core-3.1

我的单元测试设置中有如下代码:

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为空。

DI工厂创建MyCustomProvider的lambda永远不会被调用。 MyCustomProvider的构造函数也不是实现ILogger的类的构造函数。

我猜测我缺少将其连接起来的步骤。但是,.Net Core 1、2和3之间有很多相互矛盾的文档。

我需要怎么做才能以.Net Core 3方式连接我的提供商?(这样我就可以获得使用它的ILogger。)

注意:

  • 我不打LoggingBuilder.AddProvider。但是,如果您查看该扩展名的source code,它只会调用AddSingleton,这是我的工作,但带有工厂。 (有了here这个想法。)
  • 类似地,services.RemoveAll<ILoggerProvider>()与呼叫ClearProviders相同。
  • 我的目标是模拟将ILogger注入到类构造函数中。

例如:

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

以某种方式完成此操作而不指示任何提供者。我想在单元测试中使它相同。 (只需使用我的自定义提供程序即可。)

1 个答案:

答案 0 :(得分:2)

示例中,您的logger变量为null的原因是因为ILogger实际上并未注册为服务。如果查看AddLogging的来源,则只能看到ILogger<>ILoggerFactory的寄存器。如果您曾经尝试通过.NET Core DI接受ILogger而不是ILogger<MyClass>,那么您将遇到以下异常*:

System.InvalidOperationException:'无法解析类型的服务 尝试激活时出现“ Microsoft.Extensions.Logging.ILogger” “您的服务”

基于此,您的测试代码存在缺陷,因为您一开始从未收到过ILogger。要查看您的代码实际上正在工作,请修改您的测试代码,使其取回ILogger<SomeClass>。变量的结果将为非null,并且将敲击提供程序的构造函数中设置的断点:

// 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的ITestOutputHelper注入到与您相同的注册模式的自定义记录器中,因此我从个人经验中知道这可行。但是我还测试了您的特定代码是否可以正常工作(模拟出自己的IDependency)-执行了上面的代码,并命中了MyCustomProvider的构造函数中设置的断点和服务注册。另外,如果我将记录器注入到一个类中,它也会受到打击(如预期)。

您的注释“某些情况下在不指示任何提供者的情况下这样做是错误的”,因为您实际上已经注册了提供者!但是,即使在所有提供者都被清除并且没有添加任何新提供者的情况下,您仍然会得到一个非null的记录器。这是因为LoggerFactory只是遍历每个提供程序,在它们周围建立一个新的记录程序 wrapper 。如果没有提供者,那么您实际上将获得一个无操作记录器。

*这很不幸,因为某些工具(例如R#)会建议将参数转换为基本类型ILogger,然后破坏DI!

**由于没有通用类型参数可传递给ILoggerFactory.CreateLogger,因此需要默认类别名称。对于通用ILogger<T>而言,类别名称始终是T名称的变体,但显然非通用版本无法做到这一点。如果我不得不猜测,这可能就是为什么他们默认不注册ILogger实现的原因。

相关问题