如何测试asp.net核心内置的Ilogger

时间:2016-09-20 21:43:15

标签: c# unit-testing logging asp.net-core moq

我想验证记录的一些日志。我使用的是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

那么,如何验证记录此日志?

7 个答案:

答案 0 :(得分:56)

正如@ Nkosi已经说过的那样,你无法模拟扩展方法。你应该模仿的是the ILogger.Log methodLogError 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类型,如果你需要能够测试它被调用。

https://ardalis.com/testing-logging-in-aspnet-core

答案 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);