【问题标题】:How to check that an error is logged in a unit test for ASP.NET Core 3.0?如何检查 ASP.NET Core 3.0 的单元测试中是否记录了错误?
【发布时间】:2019-10-08 22:31:26
【问题描述】:

我想创建一个单元测试以确保方法使用 xUnit 和 Moq 记录错误。此代码在 ASP.NET Core 2.1 中有效:

//Arrange
var logger = new Mock<ILogger<MyMiddleware>>();
var httpContext = new DefaultHttpContext();

var middleware = new MyMiddleware(request => Task.FromResult(httpContext), logger.Object);

//Act
await middleware.InvokeAsync(httpContext);

//Assert
logger.Verify(x => x.Log(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()), Times.Once);

验证_logger.LogError("Error message"); 是否在middleware.InvokeAsync 中被调用。

但是,在 ASP.NET Core 3.0 中,我无法验证是否正在调用记录器。 Microsoft.Extensions.Logging.Internal 不能再被引用,所以FormattedLogValues 不可用。

我尝试将Assert() 更改为使用object 而不是FormattedLogValues,以及IReadOnlyList&lt;KeyValuePair&lt;string, object&gt;&gt;,因为这是FormattedLogValues 所基于的(FormattedLogValues.cs)。

这是我在 Visual Studio 测试运行程序中收到的错误消息:

Message: 
    Moq.MockException : 
    Expected invocation on the mock once, but was 0 times: x => x.Log<object>(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>())

  Performed invocations:

    ILogger.Log<FormattedLogValues>(LogLevel.Error, 0, Error message, null, Func<FormattedLogValues, Exception, string>)

  Stack Trace: 
    Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
    Mock`1.Verify(Expression`1 expression, Times times)
    Mock`1.Verify(Expression`1 expression, Func`1 times)
    MyMiddlewareTests.InvokeAsync_ErrorIsLogged() line 35
    --- End of stack trace from previous location where exception was thrown ---

如何验证错误是否记录在 ASP.NET Core 3.0 中?

【问题讨论】:

标签: c# asp.net-core moq asp.net-core-3.0 xunit.net


【解决方案1】:

在 aspnet github 页面上有 an issue 关于这个。看来问题出在Moq 上,他们已经进行了更改以修复它。

您需要升级到 Moq 4.13 才能获得修复。

他们引入了It.IsAnyType 来解决内部对象的问题,因此您应该能够将object 引用更改为It.IsAnyType 并像这样编写测试:

logger.Verify(x => x.Log(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Once);

注意:最后一个参数需要进行类型转换,因为Moq 目前不支持嵌套类型匹配器。

更多来自Moq的详细信息可以找到here

【讨论】:

  • 如何验证传递给 Log 方法的消息?
【解决方案2】:

我使用 SGA(设置、抓取、断言)方法。

//In the setup part
var mockLog = new Mock<ILogger>(MockBehavior.Strict);
string error = null;
mockLog.Setup(l => l
    .Error(It.IsAny<Exception>(), It.IsAny<string>()))
    .Callback((Exception b, string c) =>
    {
        error = c + " " + b.GetBaseException().Message;
        // This would keep only the last error, but it's OK, since there should be zero
        // Sometimes I use a collection and append the error info.
    });


//In the assert part
Assert.IsNull(error, $"Error detected: {error}");

【讨论】:

    【解决方案3】:

    这有点晚了,但我只想补充一点,如果您要断言正在记录的特定消息,您可以创建一个自定义匹配器来检查消息:

     logger.Verify(x => x.Log(LogLevel.Error,
          It.IsAny<EventId>(),
          It.Is<It.IsAnyType>((x, _) => LogMessageMatcher(x, "Expected error message")),
         It.IsAny<Exception>(),
         It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once);
    

    MatcherMethod 将是一个静态方法,如下所示:

         public static bool LogMessageMatcher(object formattedLogValueObject, string message)
        {
            var logValues = formattedLogValueObject as IReadOnlyList<KeyValuePair<string, object>>;
            return logValues.FirstOrDefault(logValue => logValue.Key == "{OriginalFormat}")
                             .Value.ToString() == message;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-30
      • 1970-01-01
      • 2015-06-12
      • 2013-08-22
      • 2019-12-29
      • 2013-05-29
      相关资源
      最近更新 更多