Как издеваться над методами ILogger.LogXXX в netcore 3.0

До сих пор мы издевались над ILogger.LogXXXпризывает, следуя этому подходу.

К сожалению, после обновления проекта до.net core 3.0, и если вы используете строгие mocks (Moq), он всегда будет жаловаться на отсутствие соответствующей настройки:

Moq.MockException : ILogger.Log<FormattedLogValues>(LogLevel.Information, 0, 
           Inicio de cancelamento de reserva:  Grm.GestaoFrotas.Dtos.Reservas.Mensagens.MsgCancelamentoReserva, 
           null, 
           Func<FormattedLogValues, Exception, string>) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.

К сожалению, я не могу просто изменить объект с помощью FormattedLogValues нравится:

_container.GetMock<ILogger<GestorReservas>>()
          .Setup(l => l.Log(It.IsAny<LogLevel>(),
                            It.IsAny<EventId>(),
                            It.IsAny<FormattedLogValues>(),
                            It.IsAny<Exception>(),
                            It.IsAny<Func<FormattedLogValues, Exception, string>()));

Это не сработает, потому что FormattedLogValues внутренний.

Я всегда могу изменить стратегию издевательства (строгий на свободный), но я бы предпочел оставить его таким, какой он есть (строгий). Итак, есть какие-нибудь подсказки о том, как решить эту проблему?

Спасибо.

3 ответа

Попробуй это:

//    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
//    <PackageReference Include="Moq" Version="4.13.1" />

container.GetMock<ILogger<GestorReservas>>()
          .Setup(l => l.Log(It.IsAny<LogLevel>(),
                            It.IsAny<EventId>(),
                            It.IsAny<It.IsAnyType>(),
                            It.IsAny<Exception>(),
                            (Func<It.IsAnyType, Exception, string>)It.IsAny<object>())));

https://www.bountysource.com/issues/79804378-cannot-verify-calls-to-ilogger-in-net-core-3-0-preview-8-generic-type-matcher-doesn-t-work-with-verify

Это расширяет ответ Мука.

Мое дополнение - "ILoggerFactory"... и то, как вы "кодируете" собственно логирование внутри вашего реального класса.

Во-первых, код модульного теста:

      using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;


    private Mock<ILoggerFactory> GetDefaultILoggerFactoryMock()
    {
        Mock<ILoggerFactory> returnMock = new Mock<ILoggerFactory>(MockBehavior.Loose);
        returnMock.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(
            () =>
                this.GetDefaultILoggerMock<MyConcreteClassThatUsesILoggerFactoryInItsConstructor>().Object);
        return returnMock;
    }

    private Mock<ILogger<T>> GetDefaultILoggerMock<T>()
    {
        Mock<ILogger<T>> returnMock = new Mock<ILogger<T>>(MockBehavior.Strict);
        returnMock.Setup(
            m => m.Log(
                It.IsAny<LogLevel>(),
                It.IsAny<EventId>(),
                It.IsAny<object>(),
                It.IsAny<Exception>(),
                It.IsAny<Func<object, Exception, string>>())).Callback(
            (LogLevel ll, EventId eid, object obj1, Exception ex, Func<object, Exception, string> func) =>
            {
                Console.WriteLine(func.Invoke(obj1, ex));
            }).Verifiable();
        returnMock.Setup(m => m.IsEnabled(It.IsAny<LogLevel>())).Returns(false);
        return returnMock;
    }

Теперь в вашем реальном классе (здесь я называю его «MyConcreteClassThatUsesILoggerFactoryInItsConstructor») .. вам нужно использовать МЕТОД НЕ РАСШИРЕНИЯ для выполнения вызовов журнала. (Все вспомогательные методы, такие как LogInformation, являются перегрузками статических методов расширения)

Ну вот так:

                  Func<object, Exception, string> logMsgFunc = (a, b) => "MyDebugMessageOne";
            this.logger.Log(LogLevel.Debug, ushort.MaxValue, null, null, logMsgFunc);

или для того, что является исключением

              catch (Exception ex)
        {
            Func<object, Exception, string> logMsgFunc = (a, b) => "MyErrorContextMessageTwo";
            this.logger.Log(LogLevel.Error, ushort.MaxValue, null, ex, logMsgFunc);
        }

Если вы выполните обе эти вещи, особенно вторую часть, вы можете издеваться над ILoggerFactory / ILogger

Вот необходимые части класса / конструктора:

      using Microsoft.Extensions.Logging;

public class MyConcreteClassThatUsesILoggerFactoryInItsConstructor : IMyConcreteClassThatUsesILoggerFactoryInItsConstructor
{
    public const string ErrorMsgILoggerFactoryIsNull = "ILoggerFactory is null";

    private readonly ILogger<MyConcreteClassThatUsesILoggerFactoryInItsConstructor> logger;
    

    public MyConcreteClassThatUsesILoggerFactoryInItsConstructor(
        ILoggerFactory loggerFactory)
    {
        if (null == loggerFactory)
        {
            throw new ArgumentNullException(ErrorMsgILoggerFactoryIsNull, (Exception)null);
        }

        this.logger = loggerFactory.CreateLogger<MyConcreteClassThatUsesILoggerFactoryInItsConstructor>();

    }

    public void DoSomething()
    {

        Func<object, Exception, string> logMsgFunc = (a, b) => "DoSomething Started";
        this.logger.Log(LogLevel.Debug, ushort.MaxValue, null, null, logMsgFunc);

        int div = 0;

        try
        {
            int x = 1 / div;
        }
        catch (Exception ex)
        {
            Func<object, Exception, string> errorMsgFunc = (a, b) => string.Format("MyErrorContextMessageTwo (div='{0}')", div);
            this.logger.Log(LogLevel.Error, ushort.MaxValue, null, ex, errorMsgFunc);
        }


    }

}

https://adamstorr.azurewebsites.net/blog/mocking-ilogger-with-moq описывает это с помощью решения только Moq.

      loggerMock
  .Verify(l => l.Log(
    It.Is<LogLevel>(l => l == LogLevel.Information),
    It.IsAny<EventId>(),
    It.Is<It.IsAnyType>((v, t) => v.ToString() == "Message"),
    It.IsAny<Exception>(),
    It.IsAny<Func<It.IsAnyType, Exception, string>>()), 
    Times.AtLeastOnce);
Другие вопросы по тегам