如何通过最小起订量在.NetCore单元测试中验证ILogger?

Adv*_*emy 0 c# moq

我正在尝试通过 Moq 在我的项目(.net core 3.1)中测试 ILogger(Microsoft.Extensions.Logging),但是...它失败了..我试图捕获代码中的异常:

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

我定义了模拟记录器

private readonly Mock<ILogger<MyClass>> _logger = new Mock<ILogger<MyClass>>();
Run Code Online (Sandbox Code Playgroud)

这是我的 UT

_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);
Run Code Online (Sandbox Code Playgroud)

我调试了它,在我的代码中执行了日志异常方法,但最后 UT 失败,因为我收到以下消息:

预期在模拟上调用一次,但实际调用次数为 0 次: x => x.Log<It.IsAnyType>(It.IsAny(), It.IsAny(), It.Is<It.IsAnyType>((v, t) => True), It.IsAny(), It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => True))

rgv*_*lee 5

看起来您没有发布工作示例;我假设您的匿名对象是您的日志消息,并且您使用的是本机 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
{

}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

更改为 Times.Never 并按预期失败:

在此输入图像描述

您的测试是否调用了 SUT 中的该代码块?

一些额外的建议:

更好的做法是在日志消息中使用占位符

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

然后记录的值(如果合适的话可以是对象)可以由日志聚合器解析。

断言日志调用可能很痛苦,有几个库可以让它变得更容易一些。Moq.Contrib.ExpressionBuilders.Logging是我专门编写的,因为:

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);
Run Code Online (Sandbox Code Playgroud)