使用Moq和xUnit模拟和测试LogError消息

6

我有一个类级别的ILogger,在构造函数中使用ILoggerFactory进行设置。然后在该类中的方法中使用logger,这个过程完美地运作。

我不知道如何Mock 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个回答

7

这是对我有效的,成功验证 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);

3
您可以像这样做:
(注意:在此特定情况下,这种方法不起作用,因为 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;
} 

需要翻译的内容:

需要注意的关键点是在设置ILogger模拟后调用Verifiable()。您可以在此SO问题中了解更多有关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。这只是针对特定的测试进行简化 - 当然,您可以更加详细。


非常感谢您的建议。然而,我尝试了这个方法,现在在MockLoggerFactory方法中出现以下错误:System.NotSupportedException:“扩展方法上的无效设置:log => log.LogError(It.IsAny<String>(), new[] { })”。它在这一行崩溃:_logger.Setup(log => log.LogError(It.IsAny<string>())).Verifiable(); - steeee
啊对了,ILogger 的东西几乎完全是用扩展方法完成的。我会更新我的答案。 - germi
我采用了你提供的FakeLogger解决方案,因为我们Moq版本(4.8.1)无法使用IsAnyType。而且目前没有升级到13.1的计划。这个FakeLogger解决方案运行得非常好!非常感谢,你真是个超级棒的人! - steeee
如果这个回答解决了你的问题,请考虑点赞/接受它以标记你的问题已解决。 - germi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接