我想验证记录的一些日志。我使用的是asp.net核心内置的ILogger,并将其注入内置的DI.net内核:
private readonly ILogger<InvoiceApi> _logger;
public InvoiceApi(ILogger<InvoiceApi> logger)
{
_logger = logger;
}
然后我使用它:_logger.LogError("error));
我试图通过以下方式嘲笑它(使用moq):
MockLogger = new Mock<ILogger<InvoiceApi>>();
并将其注入服务中以供测试:
new InvoiceApi(MockLogger.Object);
然后尝试验证:
MockLogger.Verify(m => m.LogError(It.Is<string>(s => s.Contains("CreateInvoiceFailed"))));
但它抛出:
对非虚拟(VB中可覆盖)成员的验证无效:m =&gt; m.LogError
那么,如何验证记录此日志?
答案 0 :(得分:56)
正如@ Nkosi已经说过的那样,你无法模拟扩展方法。你应该模仿的是the ILogger.Log
method,LogError
calls into。它使验证码有点笨拙,但它应该有效:
MockLogger.Verify(
m => m.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.Is<FormattedLogValues>(v => v.ToString().Contains("CreateInvoiceFailed")),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>()
)
);
(不确定这是否编译,但你得到了要点)
答案 1 :(得分:5)
我写了一个short article来展示各种方法,包括模拟底层的Log()方法,如其他答案中所述。该文章包括a full GitHub repo with each of the different options。最后,我的建议是使用你自己的适配器,而不是直接使用ILogger类型,如果你需要能够测试它被调用。
答案 2 :(得分:3)
在升级到.net core 3.1之后,FormattedLogValues成为内部的。我们无法再访问它。我做了一些扩展的扩展方法。 扩展方法的一些示例用法:
mockLogger.VerifyLog(Times.Once);
public static void VerifyLog<T>(this Mock<ILogger<T>> mockLogger, Func<Times> times)
{
mockLogger.Verify(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => true),
It.IsAny<Exception>(),
It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)), times);
}
答案 3 :(得分:1)
LogError
是一种扩展方法(静态),而不是实例方法。你不能用模拟框架“直接”模拟静态方法(因此扩展方法),因此Moq无法模拟并因此验证该方法。我已经在网上看到有关调整/包装目标界面并对其进行模拟的建议,但如果您在许多地方使用了整个代码中的默认ILogger
,则意味着重写。您必须创建2个新类型,一个用于包装类,另一个用于可模拟接口。
答案 4 :(得分:1)
对于那些试图接收日志调用回调的人:
mock.Mock<ILogger<X>>()
.Setup(x =>
x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()
)
)
.Callback<LogLevel, EventId, object, Exception, Delegate>(
(level, eventid, state, ex, func) =>
{
this.Out.WriteLine(state.ToString());
}
);
答案 5 :(得分:1)
有一个 github issue 向您展示了如何验证记录器的使用情况。
假设我们有一个要模拟的 ILogger<MyService>
:
private readonly Mock<ILogger<MyService>> loggerMock = new Mock<ILogger<MyService>>();
loggerMock.Verify(l => l.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
Times.Never);
const LogLevel expectedLevel = LogLevel.Debug;
Times expectedOccasions = Times.Exactly(1);
loggerMock.Verify(l => l.Log(expectedLevel, It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
expectedOccasions);
const LogLevel expectedLevel = LogLevel.Information;
const string expectedMessage = "Information";
const Exception withoutException = null;
Times expectedOccasions = Times.Once;
loggerMock.Verify(logger => logger.Log(expectedLevel, It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(), withoutException,
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
expectedOccasions, expectedMessage);
或
const LogLevel expectedLevel = LogLevel.Information;
const string expectedMessage = "Information";
const Exception withoutException = null;
Times expectedOccasions = Times.Once;
loggerMock.Verify(logger => logger.Log(expectedLevel, It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, _) => v.ToString().Contains(expectedMessage)),
withoutException, (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
expectedOccasions);
const LogLevel expectedLevel = LogLevel.Critical;
Times expectedOccasions = Times.Once;
loggerMock.Verify(l => l.Log(expectedLevel , It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(), It.IsAny<MyCustomException>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
expectedOccasions);
答案 6 :(得分:0)
我必须对工作中的黑匣子流程进行相当多的记录器验证,并且在短期内忍受了这些表达式之后,我屈服了并整合了一个表达式构建器-Moq.Contrib.ExpressionBuilders.Logging。
最终结果是简短,流利的验证语句:
logger.Verify(
Log.With.LogLevel(LogLevel.Error)
.And.EventId(666)
.And.LogMessage("I am a meat popsicle"),
Times.Once);