如何验证 ILogger<T>.Log 扩展方法是否已使用 Moq 调用?
How to verify ILogger<T>.Log extension method has been called using Moq?
我创建了一个 xUnit 项目来测试这个示例代码
public class ClassToTest
{
private readonly ILogger<ClassToTest> _logger;
public ClassToTest(ILogger<ClassToTest> logger)
{
_logger = logger;
}
public void Foo() => _logger.LogError(string.Empty);
}
我安装了 Moq 来为记录器创建模拟实例
public class ClassToTestTests
{
private readonly ClassToTest _classToTest;
private readonly Mock<ILogger<ClassToTest>> _loggerMock;
public ClassToTestTests()
{
_loggerMock = new Mock<ILogger<ClassToTest>>();
_classToTest = new ClassToTest(_loggerMock.Object);
}
[Fact]
public void TestFoo()
{
_classToTest.Foo();
_loggerMock.Verify(logger => logger.LogError(It.IsAny<string>()), Times.Once);
}
}
当 运行 测试时我收到此错误消息
System.NotSupportedException: Unsupported expression: logger =>
logger.LogError(It.IsAny(), new[] { })
System.NotSupportedException Unsupported expression: logger =>
logger.LogError(It.IsAny(), new[] { }) Extension methods
(here: LoggerExtensions.LogError) may not be used in setup /
verification expressions.
经过一番研究,我知道所有的日志方法都只是扩展方法。 Moq 无法设置扩展方法。
我想避免为这个问题安装额外的第三方包。有什么办法可以让测试通过吗?
您不能模拟扩展方法。
而不是嘲笑
logger.LogError(...)
你需要模拟接口方法
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
LogError 实际上是这样调用那个接口方法的
logger.Log(LogLevel.Error, 0, new FormattedLogValues(message, args), null, (state, ex) => state.ToString());
所以你需要模拟
_loggerMock.Verify(logger => logger.Log(It.Is(LogLevel.Error), It.Is(0), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<TState, Exception, string>>()), Times.Once);
免责声明我没有验证代码
编辑在pinkfloydx33的评论后,我在.net50中设置了一个测试示例并得出以下答案
使用最新的框架,FormattedLogValues class 已成为内部。所以你不能将它与通用 Moq.It 成员一起使用。但是 Moq 有另一种方法可以做到这一点(这个 answer 也提到了解决方案)
像这样调用记录器
_logger.LogError("myMessage");
您需要这样验证
_loggerMock.Verify(logger => logger.Log(
It.Is<LogLevel>(logLevel => logLevel == LogLevel.Error),
It.Is<EventId>(eventId => eventId.Id == 0),
It.Is<It.IsAnyType>((@object, @type) => @object.ToString() == "myMessage" && @type.Name == "FormattedLogValues"),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
Times.Once);
您将 It.IsAnyType 用于您无权访问的类型。如果您需要限制验证,您可以添加一个 func
当您使用字符串消息和一些参数时,您需要将 FormattedLogValues 类型的对象转换为接口 IReadOnlyList> 并验证不同参数的 string/values。
我创建了一个 xUnit 项目来测试这个示例代码
public class ClassToTest
{
private readonly ILogger<ClassToTest> _logger;
public ClassToTest(ILogger<ClassToTest> logger)
{
_logger = logger;
}
public void Foo() => _logger.LogError(string.Empty);
}
我安装了 Moq 来为记录器创建模拟实例
public class ClassToTestTests
{
private readonly ClassToTest _classToTest;
private readonly Mock<ILogger<ClassToTest>> _loggerMock;
public ClassToTestTests()
{
_loggerMock = new Mock<ILogger<ClassToTest>>();
_classToTest = new ClassToTest(_loggerMock.Object);
}
[Fact]
public void TestFoo()
{
_classToTest.Foo();
_loggerMock.Verify(logger => logger.LogError(It.IsAny<string>()), Times.Once);
}
}
当 运行 测试时我收到此错误消息
System.NotSupportedException: Unsupported expression: logger => logger.LogError(It.IsAny(), new[] { })
System.NotSupportedException Unsupported expression: logger => logger.LogError(It.IsAny(), new[] { }) Extension methods (here: LoggerExtensions.LogError) may not be used in setup / verification expressions.
经过一番研究,我知道所有的日志方法都只是扩展方法。 Moq 无法设置扩展方法。
我想避免为这个问题安装额外的第三方包。有什么办法可以让测试通过吗?
您不能模拟扩展方法。
而不是嘲笑
logger.LogError(...)
你需要模拟接口方法
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
LogError 实际上是这样调用那个接口方法的
logger.Log(LogLevel.Error, 0, new FormattedLogValues(message, args), null, (state, ex) => state.ToString());
所以你需要模拟
_loggerMock.Verify(logger => logger.Log(It.Is(LogLevel.Error), It.Is(0), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<TState, Exception, string>>()), Times.Once);
免责声明我没有验证代码
编辑在pinkfloydx33的评论后,我在.net50中设置了一个测试示例并得出以下答案
使用最新的框架,FormattedLogValues class 已成为内部。所以你不能将它与通用 Moq.It 成员一起使用。但是 Moq 有另一种方法可以做到这一点(这个 answer 也提到了解决方案)
像这样调用记录器
_logger.LogError("myMessage");
您需要这样验证
_loggerMock.Verify(logger => logger.Log(
It.Is<LogLevel>(logLevel => logLevel == LogLevel.Error),
It.Is<EventId>(eventId => eventId.Id == 0),
It.Is<It.IsAnyType>((@object, @type) => @object.ToString() == "myMessage" && @type.Name == "FormattedLogValues"),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
Times.Once);
您将 It.IsAnyType 用于您无权访问的类型。如果您需要限制验证,您可以添加一个 func
当您使用字符串消息和一些参数时,您需要将 FormattedLogValues 类型的对象转换为接口 IReadOnlyList