如何Mock ILogger的LogInformation方法以避免System.NotSupportedException: Unsupported expression异常?
解决Moq Mock ILogger扩展方法时的NotSupportedException问题
这个问题我之前也踩过坑,核心原因很明确:你想Mock的LogInformation是ILogger的扩展方法,而Moq只能Mock接口或类的实例方法,没法直接对扩展方法进行Setup/验证操作。下面给你两种实用的解决思路:
方法一:直接Mock ILogger的底层Log实例方法
所有的LogInformation、LogError等扩展方法,最终都会调用ILogger接口定义的Log实例方法。所以咱们可以直接对这个底层方法进行Setup:
修改你的测试代码:
[Fact()] public async Task GetSearchFields_WhenCalled_ReturnsSearchOptionsMetaData() { // Arrange var expectedMessage = "Received request for GetSearchFields"; // 直接Setup底层的Log方法,匹配LogLevel.Information和目标消息 _mockloggerController.Setup(x => x.Log( LogLevel.Information, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => v.ToString().Contains(expectedMessage)), It.IsAny<Exception>(), It.IsAny<Func<It.IsAnyType, Exception, string>>() )); // Act var metaDataController = this.CreateMetaDataController(); var result = await metaDataController.GetSearchFields().ConfigureAwait(false); // Assert // 验证Log方法是否被调用过 _mockloggerController.Verify(x => x.Log( LogLevel.Information, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => v.ToString().Contains(expectedMessage)), It.IsAny<Exception>(), It.IsAny<Func<It.IsAnyType, Exception, string>>() ), Times.Once); }
这里的关键是:
- 匹配
LogLevel.Information来对应LogInformation的调用 - 用
It.IsAnyType来匹配日志的结构化参数(因为扩展方法会把消息包装成一个FormattedLogValues对象) - 通过
v.ToString()来检查日志消息是否符合预期
方法二:使用Moq.Extensions.Logging简化操作
如果你觉得手动Mock底层Log方法太繁琐,可以使用官方提供的Moq.Extensions.Logging NuGet包,它专门为ILogger的Mock做了封装,用起来更简洁:
步骤1:安装NuGet包
在Package Manager Console里执行:
Install-Package Moq.Extensions.Logging
或者用.NET CLI:
dotnet add package Moq.Extensions.Logging
步骤2:修改测试代码
public class MetaDataControllerTests { // 替换原来的Mock<ILogger<MetaDataController>>为MockLogger<MetaDataController> private readonly MockLogger<MetaDataController> _mockloggerController; public MetaDataControllerTests() { _mockloggerController = new MockLogger<MetaDataController>(); } private MetaDataController CreateMetaDataController() { return new MetaDataController(_mockloggerController.Object); } [Fact()] public async Task GetSearchFields_WhenCalled_ReturnsSearchOptionsMetaData() { // Arrange // 不需要额外Setup,直接使用扩展方法验证 // Act var metaDataController = this.CreateMetaDataController(); var result = await metaDataController.GetSearchFields().ConfigureAwait(false); // Assert // 使用VerifyLogInformation直接验证日志 _mockloggerController.VerifyLogInformation("Received request for GetSearchFields", Times.Once); } }
这种方法的优势是完全不用关心底层的Log方法,直接用和业务代码一致的LogInformation相关的验证方法,代码可读性更高。
补充说明
- 如果你用的是xUnit + Microsoft.Extensions.Logging的测试框架,还可以考虑使用
Microsoft.Extensions.Logging.Testing包中的FakeLogger,它也能方便地捕获和验证日志。 - 不管用哪种方法,都要注意:如果你的日志消息包含占位符(比如
LogInformation("User {UserId} logged in", userId)),验证的时候要匹配完整的格式化消息,或者用参数匹配。
内容的提问来源于stack exchange,提问作者abbas arman
相关产品推荐
相关产品推荐

