【问题标题】:Mocking and testing the LogError message using Moq and xUnit使用 Moq 和 xUnit 模拟和测试 LogError 消息
【发布时间】:2019-10-30 16:55:09
【问题描述】:

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

【问题讨论】:

    标签: c# mocking ilogger


    【解决方案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;
    } 
    

    要注意的关键是在设置ILogger 模拟后调用Verifiable()。您可以在this SO question 中阅读更多关于Verifiable() 的信息。测试运行后,您可以通过在 mock 上调用 .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&lt;ILoggerFactory&gt; 返回:

    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: 'Invalid setup on an extension method: log => log.LogError(It.IsAny(), new[] { })' 它在这一行崩溃:_logger.Setup(log => log.LogError(It.IsAny())).Verifiable();
    • 啊,对,ILogger 的东西几乎完全是用扩展方法完成的。我会更新我的答案。
    • 我选择了您的 FakeLogger 解决方案,因为我无法在我们的 Moq (4.8.1) 版本上使用 IsAnyType。目前没有升级到 13.1 的计划。 FakeLogger 解决方案很有效!!非常感谢你是明星!
    • 如果这回答了您的问题,请考虑支持/接受它以标记您的问题已解决。
    【解决方案2】:

    这终于为我工作了,成功验证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);
    

    【讨论】:

      猜你喜欢
      • 2018-01-26
      • 2019-09-27
      • 2014-01-24
      • 1970-01-01
      • 2021-08-17
      • 1970-01-01
      • 2021-12-04
      • 2019-12-25
      • 1970-01-01
      相关资源
      最近更新 更多