升级到 .NET Core 3.1 后,Moq 验证未按预期工作
Moq Verify Not Working as Expected After Upgrading to .NET Core 3.1
正如标题所暗示的,这在 .NET Core 2.2 和 Moq 版本 4.10.1 中对我们有用。升级到 .NET Core 3.1 和 Moq 版本 4.14.5 后,验证方法失败,表示尚未调用指定的方法(底层代码没有更改)。我将 Moq 回滚到版本 4.10.1 只是为了看看它是否是由于新版本的 Moq 中的更改所致。我仍然遇到同样的错误。
正在尝试验证日志消息是否已写入 ILogger。
奇怪的是,如果我调试单元测试并用变量监视查看模拟的 object,它表明该方法确实已被调用。
相关代码:
public class AuditFilter, IResultFilter
{
...
public void OnResultExecuted( ResultExecutedContext context )
{
if( !IsContextValid( context ) )
{ return; }
...
}
public override bool IsContextValid( FilterContext context )
{
if( context == null )
{
Logger.Error( "Error writing to the audit log", new ArgumentNullException( nameof( context ) ) );
return false;
}
if( context.HttpContext == null )
{
Logger.LogError( "Error writing to the audit log", new ArgumentNullException( nameof( context.HttpContext ) ) );
return false;
}
return true;
}
public ILogger Logger { get; set; }
...
}
public class AuditTests : BaseTests
{
...
private Mock<ILogger> _mockLog;
private AuditFilter _filter;
[SetUp]
public void Setup()
{
_mockLog = new Mock<Microsoft.Extensions.Logging.ILogger>();
_mockLog.SetupAllProperties;
_filter = new AuditFilter();
}
[Test]
public void Service_Filters_Audit_IsContextValid_Context_Null()
{
var expected = false;
_filter.Logger = _mockLog.Object;
var actual = _filter.IsContextValid( null );
_mockLog.Verify( logger => logger.Log( LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<object>(),
It.IsAny<ArgumentNullException>(),
It.IsAny<Func<object, Exception, string>>() ),
Times.Once );
Assert.AreEqual( expected, actual );
}
...
}
注:方法ILogger.LogError是微软ILogger的扩展方法,调用ILogger.Log方法
下面是抛出的异常。
注意: 整数隐式转换为 Microsoft.Extensions.Logging.EventId,ILogger.Log 方法的第二个输入参数类型。
这些签名似乎与我匹配;所以我不确定为什么它说它没有被调用。
重申一下,此代码在升级到 .NET Core 3.1 之前有效,并且在我们的 pre-forked 代码中仍然有效。
此外,在有人建议需要设置该方法之前:它在没有它的情况下在升级之前工作,我已经尝试过但它没有工作。
我认为问题出在验证表达式的这一部分
It.IsAny<Func<object, Exception, string>>()
在幕后它不是 Func<object, Exception, string>
并且模拟 IsAny 匹配器由于这种差异而不匹配。如果修改为
(Func<object, Exception, string>) It.IsAny<object>()
它应该匹配。以下是我确定的验证表达式的起点,因为它至少适用于 .NET Core 2.* 以上版本:
loggerMock.Verify(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<IReadOnlyList<KeyValuePair<string, object>>>(),
It.IsAny<Exception>(),
(Func<object, Exception, string>) It.IsAny<object>()),
Times.Exactly(expectedNumberOfInvocations));
您可以使用 It.IsAny<object>()
作为第三个参数,但我发现这在查询属性时更方便。
我最近在工作中为黑盒进程做了一些日志调用验证,所以现在我主要使用 Moq.Contrib.ExpressionBuilders.Logging 来构建流畅、可读的验证表达式。免责声明:我是作者。
就像我在评论中添加的那样,这是一个 known issue 带有 Moq 和 .NET Core 的 .NET Core 3.0 预览版。请查看 github 问题以了解更新失败的原因。
简而言之,从链接的问题来看,这里的失败是由于传递给 Logger.Log
方法的类型现在更改为 FormattedLogValues
造成的。尽管在 Moq 4.13.0 中引入了通用类型匹配器 It.IsAnyType
,但这并不能解决问题。
之所以这样,作为Moq的作者之一states here
The important bit (for now) is that It.IsAnyType
is not used in a nested position in the It.IsAny<>
type argument.
作为解决方法,我们应该更换
It.IsAny<Func<object, Exception, string>>()
和
(Func<object, Exception, string>)It.IsAny<object>()
为此提供的基本原理是为了提高类型匹配器的性能,因为前者会分解引擎盖下的所有类型参数,以查看是否首先包含类型匹配器。
正如标题所暗示的,这在 .NET Core 2.2 和 Moq 版本 4.10.1 中对我们有用。升级到 .NET Core 3.1 和 Moq 版本 4.14.5 后,验证方法失败,表示尚未调用指定的方法(底层代码没有更改)。我将 Moq 回滚到版本 4.10.1 只是为了看看它是否是由于新版本的 Moq 中的更改所致。我仍然遇到同样的错误。
正在尝试验证日志消息是否已写入 ILogger。
奇怪的是,如果我调试单元测试并用变量监视查看模拟的 object,它表明该方法确实已被调用。
相关代码:
public class AuditFilter, IResultFilter
{
...
public void OnResultExecuted( ResultExecutedContext context )
{
if( !IsContextValid( context ) )
{ return; }
...
}
public override bool IsContextValid( FilterContext context )
{
if( context == null )
{
Logger.Error( "Error writing to the audit log", new ArgumentNullException( nameof( context ) ) );
return false;
}
if( context.HttpContext == null )
{
Logger.LogError( "Error writing to the audit log", new ArgumentNullException( nameof( context.HttpContext ) ) );
return false;
}
return true;
}
public ILogger Logger { get; set; }
...
}
public class AuditTests : BaseTests
{
...
private Mock<ILogger> _mockLog;
private AuditFilter _filter;
[SetUp]
public void Setup()
{
_mockLog = new Mock<Microsoft.Extensions.Logging.ILogger>();
_mockLog.SetupAllProperties;
_filter = new AuditFilter();
}
[Test]
public void Service_Filters_Audit_IsContextValid_Context_Null()
{
var expected = false;
_filter.Logger = _mockLog.Object;
var actual = _filter.IsContextValid( null );
_mockLog.Verify( logger => logger.Log( LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<object>(),
It.IsAny<ArgumentNullException>(),
It.IsAny<Func<object, Exception, string>>() ),
Times.Once );
Assert.AreEqual( expected, actual );
}
...
}
注:方法ILogger.LogError是微软ILogger的扩展方法,调用ILogger.Log方法
下面是抛出的异常。
注意: 整数隐式转换为 Microsoft.Extensions.Logging.EventId,ILogger.Log 方法的第二个输入参数类型。
这些签名似乎与我匹配;所以我不确定为什么它说它没有被调用。
重申一下,此代码在升级到 .NET Core 3.1 之前有效,并且在我们的 pre-forked 代码中仍然有效。
此外,在有人建议需要设置该方法之前:它在没有它的情况下在升级之前工作,我已经尝试过但它没有工作。
我认为问题出在验证表达式的这一部分
It.IsAny<Func<object, Exception, string>>()
在幕后它不是 Func<object, Exception, string>
并且模拟 IsAny 匹配器由于这种差异而不匹配。如果修改为
(Func<object, Exception, string>) It.IsAny<object>()
它应该匹配。以下是我确定的验证表达式的起点,因为它至少适用于 .NET Core 2.* 以上版本:
loggerMock.Verify(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<IReadOnlyList<KeyValuePair<string, object>>>(),
It.IsAny<Exception>(),
(Func<object, Exception, string>) It.IsAny<object>()),
Times.Exactly(expectedNumberOfInvocations));
您可以使用 It.IsAny<object>()
作为第三个参数,但我发现这在查询属性时更方便。
我最近在工作中为黑盒进程做了一些日志调用验证,所以现在我主要使用 Moq.Contrib.ExpressionBuilders.Logging 来构建流畅、可读的验证表达式。免责声明:我是作者。
就像我在评论中添加的那样,这是一个 known issue 带有 Moq 和 .NET Core 的 .NET Core 3.0 预览版。请查看 github 问题以了解更新失败的原因。
简而言之,从链接的问题来看,这里的失败是由于传递给 Logger.Log
方法的类型现在更改为 FormattedLogValues
造成的。尽管在 Moq 4.13.0 中引入了通用类型匹配器 It.IsAnyType
,但这并不能解决问题。
之所以这样,作为Moq的作者之一states here
The important bit (for now) is that
It.IsAnyType
is not used in a nested position in theIt.IsAny<>
type argument.
作为解决方法,我们应该更换
It.IsAny<Func<object, Exception, string>>()
和
(Func<object, Exception, string>)It.IsAny<object>()
为此提供的基本原理是为了提高类型匹配器的性能,因为前者会分解引擎盖下的所有类型参数,以查看是否首先包含类型匹配器。