How to test asp.net core built-in Ilogger

前端 未结 6 1445
无人共我
无人共我 2021-02-02 06:36

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:

private readonly ILogger<         


        
相关标签:
6条回答
  • 2021-02-02 07:08

    I've written a short article showing a variety of approaches, including mocking the underlying Log() method as described in other answers here. The article includes a full GitHub repo with each of the different options. 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.

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

    0 讨论(0)
  • 2021-02-02 07:08

    After some upgrades to .net core 3.1 FormattedLogValues become internal. 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);
    }
    
    0 讨论(0)
  • 2021-02-02 07:12

    LogError is an extension method (static) not an instance method. 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. 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. You would have to create 2 new types, one for the wrapper class and the other for the mockable interface.

    0 讨论(0)
  • 2021-02-02 07:14

    As @Nkosi've already said, you can't mock an extension method. What you should mock, is the ILogger.Log method, which LogError calls into. 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)

    0 讨论(0)
  • 2021-02-02 07:21

    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.

    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);
    
    0 讨论(0)
  • 2021-02-02 07:24

    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());
                        }
                    );
    
    0 讨论(0)
提交回复
热议问题