Мокинг и тестирование сообщения LogError с помощью Moq и xUnit

У меня есть ILogger уровня класса, который настраивается с помощью ILoggerFactory в конструкторе. Затем регистратор используется в методе этого класса, и это отлично работает.

Я изо всех сил пытаюсь смоделировать ILogger и ILoggerFactory, чтобы я мог выполнить модульное тестирование сообщения LogError. Кто-нибудь может показать мне пример этого?

Я использую xUnit и Microsoft.Extentions.Logging для входа в систему

//This is my unit test project
[Fact]
public void TestLogErrorMessage()
{
MyClass myClass = new MyClass  (MockLoggerFactory().Object);

    var result = myClass.Mymethod("a")

    //How do I test the LogError message???
}


//Not sure if this is correct
private Mock<ILoggerFactory> MockLoggerFactory()
{
           Mock<ILoggerFactory> mockLoggerFactory = new 
Mock<ILoggerFactory>(MockBehavior.Default);
    mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>()))
                           .Returns(MockLogger().Object);

        return mockLoggerFactory;
}

private Mock<ILogger> MockLogger()
{        
       var logger = new Mock<ILogger>();
       return logger;
}

//This is the class/method i need to test
private readonly ILogger logger;

public MyClass(ILoggerFactory loggerFactory)
{
       if (loggerFactory != null)
       {
           this.logger = loggerFactory.CreateLogger<MyClass>();
       }
}


public string Mymethod(string msg)
{
       if(msg = "a")
       {
           this.logger.LogError($"error");
       }

       return "a string";
 }

2 ответа

Это то, что наконец сработало для меня, успешная проверка вызова LogError:

      var loggerMock = new Mock<ILogger>();
        Expression<Action<ILogger>> logAction = x => x.Log<It.IsAnyType>(LogLevel.Error,//< you can change this up with other severity levels
                                         It.IsAny<EventId>(),
                                         It.IsAny<It.IsAnyType>(),
                                         It.IsAny<Exception>(),
                                         It.IsAny<Func<It.IsAnyType, Exception, string>>());

        loggerMock.Setup(logAction).Verifiable();

        //call a method that calls LogError here.
        loggerMock.Object.LogError("test");

        loggerMock.Verify(logAction, Times.Once);

Вы могли бы сделать что-то вроде этого

(примечание: это не работает в данном конкретном случае, потому чтоLogErrorявляется методом расширения, см. обновление ниже):

Mock<ILogger> _logger; // declare it somewhere where your test can access it

[Fact]
public void TestLogErrorMessage()
{
    MyClass myClass = new MyClass(MockLoggerFactory().Object);

    var result = myClass.Mymethod("a")

    _logger.Verify();
}


private Mock<ILoggerFactory> MockLoggerFactory()
{
    _logger = new Mock<ILogger>();
    _logger.Setup(log => log.LogError(It.IsAny<string>())).Verifiable();
    Mock<ILoggerFactory> mockLoggerFactory = new Mock<ILoggerFactory>(MockBehavior.Default);
    mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>()))
                           .Returns(_logger.Object);

    return mockLoggerFactory;
} 

Ключевым моментом является призыв к Verifiable() после создания ILoggerиздеваться. Вы можете прочитать немного больше оVerifiable()в этом ТАК вопросе. После запуска теста вы можете проверить, был ли вызван метод, вызвав.Verify() на макете.

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

Для вашего конкретного случая (попытка подтвердить звонки на ILogger.LogError) вы не должны глумиться LogError, но основной метод ILogger.Log. Это могло выглядеть так:

var formatter = new Func<It.IsAnyType, Exception, string>((a, b) => "");
m.Setup(x => x.Log<It.IsAnyType>(LogLevel.Error, 
                                 It.IsAny<EventId>(),
                                 It.IsAny<It.IsAnyType>(),
                                 It.IsAny<Exception>(), 
                                 formatter))
  .Verifiable();

Другой альтернативой было бы создание поддельной реализации ILogger и вернуть это из Mock<ILoggerFactory>:

mockLoggerFactory.Setup(_ => _.CreateLogger(It.IsAny<string>())
                 .Returns(new FakeLogger());

public class FakeLogger : ILogger
{
    public static bool LogErrorCalled { get; set; }

    public IDisposable BeginScope<TState>(TState state)
    {
        throw new NotImplementedException();
    }

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if(logLevel == LogLevel.Error)
        {
            LogErrorCalled = true;
        }
    }
}

А затем после теста вы можете проверить, FakeLogger.LogErrorCalled является true. Это упрощено для вашего конкретного теста - вы, конечно, можете сделать более подробный.

Другие вопросы по тегам