简体   繁体   English

如何测试asp.net core内置的Ilogger

[英]How to test asp.net core built-in Ilogger

I want to verify some logs logged.我想验证一些日志记录。 I am using the asp.net core built-in ILogger, and inject it with the asp.net core built-in DI:我正在使用 asp.net core 内置 ILogger,并使用 asp.net core 内置 DI 注入它:

private readonly ILogger<InvoiceApi> _logger;

public InvoiceApi(ILogger<InvoiceApi> logger)
{
    _logger = logger;
}

then I use it like: _logger.LogError("error));然后我像这样使用它: _logger.LogError("error));

I tried to mock it (with moq) as usual by:我试图像往常一样模拟它(使用最小起订量):

MockLogger = new Mock<ILogger<InvoiceApi>>();

and inject this in the service for test by:并通过以下方式将其注入服务进行测试:

new InvoiceApi(MockLogger.Object);

then tried verify:然后尝试验证:

MockLogger.Verify(m => m.LogError(It.Is<string>(s => s.Contains("CreateInvoiceFailed"))));

but it throw:但它抛出:

Invalid verify on a non-virtual (overridable in VB) member: m => m.LogError对非虚拟(在 VB 中可覆盖)成员的无效验证:m => m.LogError

So, how can I verify this logs logged?那么,我如何验证记录的这个日志?

As @Nkosi've already said, you can't mock an extension method.正如@Nkosi 已经说过的,您不能模拟扩展方法。 What you should mock, is the ILogger.Log method , which LogError calls into .应该模拟的是ILogger.Log方法LogError调用到. It makes the verification code a bit clunky, but it should work:它使验证码有点笨拙,但它应该可以工作:

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>>()
    )
);

(Not sure if this compiles, but you get the gist) (不确定这是否编译,但你明白了要点)

I've written a short article showing a variety of approaches, including mocking the underlying Log() method as described in other answers here.我写了一篇简短的文章,展示了各种方法,包括模拟此处其他答案中描述的底层 Log() 方法。 The article includes a full GitHub repo with each of the different options .本文包含一个完整的 GitHub 存储库,其中包含每个不同的选项 In the end, my recommendation is to use your own adapter rather than working directly with the ILogger type, if you need to be able to test that it's being called.最后,如果您需要能够测试它是否被调用,我的建议是使用您自己的适配器而不是直接使用 ILogger 类型。

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

After some upgrades to .net core 3.1 FormattedLogValues become internal.在对 .net core 3.1 进行一些升级后,FormattedLogValues 变为内部。 We can not access it anymore.我们不能再访问它了。 I made a extensions method with some changes.我做了一些更改的扩展方法。 Some sample usage for extension method:扩展方法的一些示例用法:

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);
}

There is a github issue which shows you how you can Verify the logger usage.有一个github 问题向您展示了如何验证记录器的使用。

Let's suppose we have an ILogger<MyService> that we want to mock:假设我们有一个要模拟的ILogger<MyService>

private readonly Mock<ILogger<MyService>> loggerMock = new Mock<ILogger<MyService>>();

Verify zero call验证零调用

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

Verify log level and times验证日志级别和时间

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

Verify log message验证日志消息

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

Or或者

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

Verify exception type验证异常类型

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

LogError is an extension method (static) not an instance method. LogError是一个扩展方法(静态)而不是一个实例方法。 You can't "directly" mock static methods (hence extension method) with a mocking framework therefore Moq is unable to mock and hence verify that method.您不能使用模拟框架“直接”模拟静态方法(因此是扩展方法),因此 Moq 无法模拟并因此验证该方法。 I have seen suggestions online about adapting/wrapping the target interface and doing your mocks on that but that would mean rewrites if you have used the default ILogger throughout your code in many places.我在网上看到了关于调整/包装目标接口并在其上进行模拟的建议,但这意味着如果您在许多地方的代码中使用了默认的ILogger ,则需要重写。 You would have to create 2 new types, one for the wrapper class and the other for the mockable interface.您必须创建 2 种新类型,一种用于包装类,另一种用于可模拟接口。

For those trying to receive a callback for the log calls:对于那些试图接收日志调用回调的人:

            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());
                    }
                );

I have to do a fair bit of logger verification for black box processes at work and, after putting up with the expressions for a short period, I caved and put together an expression builder - Moq.Contrib.ExpressionBuilders.Logging .我必须对工作中的黑盒进程进行相当多的记录器验证,在忍受了一小段表达式之后,我屈服并组合了一个表达式构建器 - Moq.Contrib.ExpressionBuilders.Logging

The end result is a shorter, fluent, verification statement:最终结果是一个更短、更流畅的验证语句:

logger.Verify(
  Log.With.LogLevel(LogLevel.Error)
    .And.EventId(666)
    .And.LogMessage("I am a meat popsicle"), 
  Times.Once);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM