如何检查 ASP.NET Core 3.0 的单元测试中是否记录了错误?
How to check that an error is logged in a unit test for ASP.NET Core 3.0?
我想创建一个单元测试以确保方法使用 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<KeyValuePair<string, object>>
因为那是 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 中记录的错误?
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
我使用 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}");
这有点晚了,但我只想补充一点,如果您断言要记录特定的消息,您可以创建一个自定义匹配器来检查消息:
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;
}
我想创建一个单元测试以确保方法使用 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<KeyValuePair<string, object>>
因为那是 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 中记录的错误?
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
我使用 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}");
这有点晚了,但我只想补充一点,如果您断言要记录特定的消息,您可以创建一个自定义匹配器来检查消息:
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;
}