3

我有一个类级别的 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";
 }
4

2 回答 2

1

你可以做这样的事情

注意:这在这种特定情况下不起作用,因为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()您可以在这个 SO question中了解更多信息。测试运行后,您可以通过调用.Verify()mock 来检查该方法是否被调用。

根据您的需要,您可以在测试类的构造函数中进行设置(如果所有/大多数测试都需要它)或直接在测试方法中进行设置。您也可以将它与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.LogErrorCalledtrue. 这已针对您的特定测试进行了简化-您当然可以比这更详细。

于 2019-10-30T17:28:25.000 回答
0

这最终对我有用,成功验证了LogError呼叫:

var loggerMock = new Mock<ILogger>();
Expression<Action<ILogger>> logAction = x =>
    // You can change this up with other severity levels:
    x.Log<It.IsAnyType>(LogLevel.Error,
        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);
于 2021-09-29T14:14:12.853 回答