我正在尝试为控制器操作编写一些单元测试.为此,我使用XUnit和Moq.控制器在构造函数中注入ILoggerFactory.一个Moq如何进行测试?
我已经尝试为控制器类模拟一个Logger,然后模拟CreateLogger以返回模拟Logger,但是当调用LogInformation()函数时,我不断获得各种测试运行时NullReferenceExceptions.
// Logger that yields only disappointment... var mockLogger = new Mock<ILogger<JwtController>>(); mockLogger.Setup(ml => ml.Log(It.IsAny<LogLevel>(),It.IsAny<EventId>(),It.IsAny<object>(),It.IsAny<Exception>(),It.IsAny<Func<object,Exception,string>>())); var mockLoggerFactory = new Mock<ILoggerFactory>(); mockLoggerFactory.Setup(mlf => mlf.CreateLogger("JwtController")).Returns(mockLogger.Object);
解决方法
对于需要回答这个问题的人,而不是一个解决方法,我扩展了本文的工作:
https://ardalis.com/testing-logging-in-aspnet-core
包装您使用的日志框架的任何部分,无论如何这是一个好主意.从您自己的日志记录界面开始:
public interface ILog<T> { void LogError(Exception ex,string message,params object[] args); void LogInformation(string message,params object[] args); }
public class Log<T> : ILog<T> { private readonly ILogger<T> logger; public Log(ILogger<T> logger) { this.logger = logger; } public void LogError(Exception ex,params object[] args) => this.logger.LogError(ex,message,args); public void LogInformation(string message,params object[] args) => this.logger.LogInformation(message,args); }
继续,添加一个用于包装记录器工厂的接口:
public interface ILogFactory { ILog<T> CreateLog<T>(); }
并实施:
public class LogFactory : ILogFactory { private readonly ILoggerFactory loggerFactory; public LogFactory() { this.loggerFactory = new LoggerFactory(); } public ILog<T> CreateLog<T>() => new Log<T>(new Logger<T>(this.loggerFactory)); }
这些是您应该引用Microsoft.Extensions.Logging命名空间的唯一地方.在其他地方,使用您的ILog< T>而不是ILogger< T>和你的ILogFactory而不是ILoggerFactory.你通常依赖注入LoggerFactory,而不是注入包装器:
IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<ILogFactory>(new LogFactory());
在主代码中,您可以检索此LogFactory并创建特定的Log< T>为你的班级:
public class MyClass { public void MyMethod(IServiceCollection serviceCollection) { var serviceProvider = serviceCollection.BuildServiceProvider(); var logFactory = this.serviceProvider.GetrequiredService<ILogFactory>(); var log = logFactory.CreateLog<ServiceApplication>(); log.LogInformation("Hello,World!"); } }
您可以想象将MyMethod的参数从IServiceCollection更改为ILogFactory或ILog< MyClass>按要求.并且 – 重点是 – 您现在可以使用以下方法模拟上述代码:
[Fact] public void Test() { IServiceCollection serviceCollection = new ServiceCollection(); var mockLog = new Mock<ILog<MyClass>>(); var mockLogFactory = new Mock<ILogFactory>(); mockLogFactory.Setup(f => f.CreateLog<MyClass>()).Returns(mockLog.Object); serviceCollection.AddSingleton<ILogFactory>(mockLogFactory.Object); var myClass = new MyClass(); myClass.MyMethod(serviceCollection); mockLog.Verify(l => l.LogInformation("Hello,World!"),Times.Once); }
“根据您在整个应用程序中无法控制的类型,会增加耦合,并且经常成为问题和技术债务的来源.” – 史蒂夫史密斯