Как проверить ILogger в тестировании блока .NETCore по Moq?

#c# #moq

Вопрос:

Я пытаюсь протестировать ILogger(Microsoft.Расширения.Ведение журнала) в моем проекте(.net core 3.1) по Moq, however…it потерпел неудачу.. Я пытаюсь поймать исключение в своем коде:

   try{
      //do something
     }
    catch (Exception e)
            {
                _logger.LogError(new
                {
                    RequestId = requestId.ToString(),
                    Topic = "Merge files",
                    Message = "process failed",
                    Status = "Failed"
                }, e);
            }
 

И я определил макет регистратора

 private readonly Mock<ILogger<MyClass>> _logger = new Mock<ILogger<MyClass>>();
 

вот мой ответ на это

 _logger.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.Once);
 

Я отладил его, метод исключения журнала выполняется в моем коде, но, наконец, UT не удался, так как я получаю следующее сообщение:

Ожидаемый вызов на макете один раз, но был 0 раз: x => x.Log>IsAnyType>(Ит.ИсАни(), Это.ИсАни(), It.IsIsAnyType>((v, t) =>> Верно), Это.ИсАни(), It.Is<ФункцияТип IsAnyType, Исключение, строка>>>((v, t) =>>> True))

Комментарии:

1. Если вы не создали свой собственный регистратор , зачем вам тестировать первоклассный регистратор от Microsoft? Не говоря уже о том, чтобы высмеивать это

2. В блоке catch вы вызываете метод LogError в регистраторе и издеваетесь над методом журнала в UT.

Ответ №1:

Не похоже, что вы опубликовали рабочий пример; Я предполагаю, что ваш анонимный объект-это ваше сообщение журнала, и вы используете собственное расширение LogError, а не свое собственное, что будет означать, что параметр исключения должен быть перед анонимным объектом (который также, вероятно, потребуется преобразовать в строку).

В любом случае, с учетом этого, выражение проверки, которое вы предоставили, работает при тестировании. Пример LINQPad:

 void Main()
{
    var loggerMock = new Mock<ILogger<Foo>>();
    var logger = loggerMock.Object;
    var requestId = Guid.NewGuid();

    try
    {
        throw new InvalidOperationException("Bar");
    }
    catch (Exception ex)
    {
        logger.LogError(ex, new
        {
            RequestId = requestId.ToString(),
            Topic = "Merge files",
            Message = "process failed",
            Status = "Failed"
        }.ToString());
    }

    loggerMock.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.Once); 
}

public class Foo
{

}
 

введите описание изображения здесь

Переходите ко времени.Никогда, и это не срабатывает, как и ожидалось:

введите описание изображения здесь

Вызывает ли ваш тест этот блок кода в вашем SUT?

Некоторые дополнительные предложения:

Рекомендуется использовать заполнители в сообщении журнала:

 logger.LogError(ex, "Process failed; requestId: '{requestId}', topic: '{topic}', status: '{status}'", requestId.ToString(), "Merge files", "Failed");
 

Зарегистрированные значения (которые могут быть объектами, если это подходит) затем могут быть проанализированы вашим агрегатором журналов.

Утверждение вызовов журнала может быть болезненным, есть пара библиотек, которые немного облегчают это. Moq.Contrib.Создатели выражений.Ведение журнала-это то, что я написал специально из-за этого:

 logger.Verify(Log.With.LogLevel(LogLevel.Error)
                .And.LogMessage("Process failed; requestId: '{requestId}', topic: '{topic}', status: '{status}'")
                .And.LoggedValue("requestId", requestId.ToString())
                .And.LoggedValue("topic", "Merge files")
                .And.LoggedValue("status", "Failed")
                .And.ExceptionMessage("Bar"),
            Times.Once);
 

Комментарии:

1. Большое спасибо, это очень полезно для меня. И я провел глубокое исследование, может быть, выясню, почему.

Ответ №2:

Я провел глубокое исследование по этому поводу.

В моем проекте мой метод LogError() вызывает другой метод Define() в Microsoft.Расширения.Ведение журнала для выполнения операций ведения журнала.

Следуя логике этого метода, я должен издеваться над регистратором.Сначала IsEnabled(уровень журнала).

 _logger.Setup(x => x.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
 

введите описание изображения здесь

Ответ №3:

Возможно, вам нужно настроить метод LogError на макете. Кроме того, можно настроить обратный вызов метода LogError, зафиксировать ошибку и затем подтвердить.