我正在尝试为控制器操作编写一些单元测试。为此,我使用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);
我假设问题是正在调用LogInformation,这是一个扩展方法,那么如何moq呢?
答案 0 :(得分:6)
对于需要回答这个问题的人,而不是解决方法,我扩展了本文的工作:
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, string message, 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);
}
“根据您在整个应用程序中无法控制的类型,会增加耦合,并且经常成为问题和技术债务的来源。” - 史蒂夫史密斯
答案 1 :(得分:0)
它的价值:除了模拟ILoggerFactory
,还可以传递NullLoggerFactory
的实例。此NullLoggerFactory
将返回NullLogger
的实例。根据文档,这是一个:
什么都不做的简约记录器。
答案 2 :(得分:0)
我只是模拟如下所示的ILogger扩展方法,并在ILoggerFactory设置中使用值函数来返回Mock ILogger对象。
var mockLogger = new Mock<ILogger<[YOUR_CLASS_TYPE]>>();
mockLogger.Setup(
m => m.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()));
var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(() => mockLogger.Object);
这将返回模拟的Logger,您可以验证对它的任何调用。无需编写包装器或助手。