You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 22:02:37