使用 Moq 中的任何结构参数验证方法调用

Verifying method call with any struct parameter in Moq

将我的应用程序从 ASP.NET Core 2.2 迁移到 ASP.NET Core 3.0 时,我 运行 遇到以下问题:

我有一个 class 在某些情况下应该记录错误消息。这是通过在 ILogger<MyClass>.

上调用 LogError 来完成的

我曾经用我的单元测试中的以下片段来验证这一点:

Mock<ILogger<MyClass>> loggerMock = ...;
MyClass myClass = ...;

myClass.MethodThatLogsTestException();

loggerMock.Verify(l => l.Log(
    It.IsAny<LogLevel>(),
    It.IsAny<EventId>(),
    It.IsAny<object>(),
    It.IsAny<TestException>(),
    It.IsAny<Func<object, Exception, string>>())
);

问题来了:

在 ASP.NET Core 2.2 中,第三个参数(通过 It.IsAny<object>() 模拟)是内部类型 FormattedLogValues。这是 class,所以 It.IsAny<object>() 有效。在 ASP.NET Core 3.0 中,它被更改为结构,因此 It.IsAny<object>() 不再匹配它。

如何让我的 Verify() 调用在 ASP.NET Core 3.0 中工作?是否有匹配任何结构类型的 It.IsAny() 版本?

编辑: 这是一个完全可运行的片段,它在 ASP.NET Core 3.0 上失败并在 ASP.NET Core 2.2.

上成功
public class Test
{
    public class Impl
    {
        private readonly ILogger<Impl> logger;

        public Impl(ILogger<Impl> logger)
        {
            this.logger = logger;
        }

        public void Method()
        {
            logger.LogError(new Exception(), "An error occurred.");
        }
    }

    [Fact]
    public void LogsErrorOnException()
    {
        var loggerMock = new Mock<ILogger<Impl>>();
        var sut = new Impl(loggerMock.Object);

        sut.Method();

        loggerMock.Verify(l => l.Log(
            It.IsAny<LogLevel>(),
            It.IsAny<EventId>(),
            It.IsAny<object>(),
            It.IsAny<Exception>(),
            It.IsAny<Func<object, Exception, string>>())
        );
    }
}

It.IsAny<Func<object, Exception, string>>()) 更改为 (Func<object, Exception, string>) It.IsAny<object>() 似乎可以解决问题。如果您使用的是 Moq 4.13+,object 也可以替换为 IsAnyType

Logger class 在内部使用 FormattedLogValues 作为状态参数(在我的示例中为 object)。结构的变化似乎与它有关。具体是什么原因我也不清楚。但是 Moq GitHub repo 上似乎有一个问题描述了一些更多的细节。似乎还没有具体的解释为什么它曾经有效,但可能很快就会发布更多信息。

https://github.com/moq/moq4/issues/918

我在 Github 中发现了同样的问题。

我对解决方案做了一个扩展方法:

public static void VerifyLog<T>(this Mock<ILogger<T>> mockLogger, Func<Times> times)
{
    mockLogger.Verify(x => x.Log(
        It.IsAny<LogLevel>(),
        It.IsAny<EventId>(),
        It.Is<It.IsAnyType>((v, t) => true),
        It.IsAny<Exception>(),
        It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)), times);
}