如何正确检查 ILogger.LogCritical 是否已被 NSubstitute 调用?
How to check properly that ILogger.LogCritical has been called with NSubstitute?
我在使用 NSubstitute 检查方法 ILogger.LogCritical(...)
是否被调用时遇到了一些困难。
例如下面的代码:
[Fact]
public void TestNSubstituteAgain()
{
var logger = Substitute.For<ILogger<StockService>>();
logger.LogCritical(new Exception(), "Hey lads!");
logger.Received().Log(
LogLevel.Critical,
Arg.Any<EventId>(),
Arg.Any<object>(),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()
);
}
我遇到了异常:
Rm.Combo.App.Tests.VirtualParcels.Services.StockServiceTest.TestNSubstituteAgain
NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
Log<Object>(Critical, any EventId, any Object, any Exception, any Func<Object, Exception, String>)
Actually received no matching calls.
at NSubstitute.Core.ReceivedCallsExceptionThrower.Throw(ICallSpecification callSpecification, IEnumerable`1 matchingCalls, IEnumerable`1 nonMatchingCalls, Quantity requiredQuantity)
at NSubstitute.Routing.Handlers.CheckReceivedCallsHandler.Handle(ICall call)
at NSubstitute.Routing.Route.Handle(ICall call)
at NSubstitute.Core.CallRouter.Route(ICall call)
at NSubstitute.Proxies.CastleDynamicProxy.CastleForwardingInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at NSubstitute.Proxies.CastleDynamicProxy.ProxyIdInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.ObjectProxy_2.Log[TState](LogLevel logLevel, EventId eventId, TState state, Exception exception, Func`3 formatter)
at Rm.Combo.App.Tests.VirtualParcels.Services.StockServiceTest.TestNSubstituteAgain() in C:\Users\eperret\Desktop\combo\api\Rm.Combo.App.Tests\VirtualParcels\Services\StockServiceTest.cs:line 99
// 1] Ok the call I am making is that ext. method below:
public static void LogCritical(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Critical, exception, message, args);
}
// 2] Which forwards to that other ext. method below:
public static void Log(this ILogger logger, LogLevel logLevel, Exception exception, string message, params object[] args)
{
logger.Log(logLevel, 0, exception, message, args);
}
// 3] and... then here (still an ext. method tho):
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, string message, params object[] args)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
logger.Log(logLevel, eventId, new FormattedLogValues(message, args), exception, _messageFormatter);
}
// 4] to finally end up with this "direct" interface method:
void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter);
// Note: which is supposedly what I am gonna ask NSubstitute to check against, right? (since we cannot use NSubstitute `Received` method on ext. method, it has to be the corresponding instance method)
注意:FormattedLogValues
是一个 internal struct
所以我真的不得不使用 Arg.Any<object>
。
当我检查实际收到的呼叫时,我看到记录器有一个呼叫,但我不太确定它与我所断言的有何不同。就像我在调试单元测试时在 var re = logger.ReceivedCalls();
之类的东西上设置断点:
我真的不知道什么不被视为 NSubstitute Received()
断言的正确参数。
有什么想法吗?
如果参数匹配非常困难,我会使用 ReceivedWithAnyArgs()
(https://nsubstitute.github.io/help/received-calls/)。如果事情很简单,NSubstitute 很好。如果它变得太复杂,我倾向于只创建接口的测试版本(例如 ILogger
),并从那里的调用中注册我感兴趣的参数,并检查那些。
我最终使用 Moq 进行了特定类型的测试:
// Defining an extension method
public static void IsLogReceived(this IMock<ILogger<StockService>>logger, LogLevel level, int count = 1) =>
logger.Verify(x =>
x.Log(level,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
Times.Exactly(count));
// [...]
// and using the ext. method like that:
[Fact]
public void TestNSubstituteAgain()
{
var loggerMock = new Mock<ILogger<StockService>>();
var logger = loggerMock.Object;
logger.LogCritical(new Exception(), "Hey lads!");
loggerMock.IsLogReceived(LogLevel.Critical);
}
它不是那么准确,但取决于方法范围内发生的事情,可以完成工作。
话虽这么说,但我承认最准确的实现方式是声明一个包装类型。
对我有点帮助的资源:
我在使用 NSubstitute 检查方法 ILogger.LogCritical(...)
是否被调用时遇到了一些困难。
例如下面的代码:
[Fact]
public void TestNSubstituteAgain()
{
var logger = Substitute.For<ILogger<StockService>>();
logger.LogCritical(new Exception(), "Hey lads!");
logger.Received().Log(
LogLevel.Critical,
Arg.Any<EventId>(),
Arg.Any<object>(),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()
);
}
我遇到了异常:
Rm.Combo.App.Tests.VirtualParcels.Services.StockServiceTest.TestNSubstituteAgain
NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
Log<Object>(Critical, any EventId, any Object, any Exception, any Func<Object, Exception, String>)
Actually received no matching calls.
at NSubstitute.Core.ReceivedCallsExceptionThrower.Throw(ICallSpecification callSpecification, IEnumerable`1 matchingCalls, IEnumerable`1 nonMatchingCalls, Quantity requiredQuantity)
at NSubstitute.Routing.Handlers.CheckReceivedCallsHandler.Handle(ICall call)
at NSubstitute.Routing.Route.Handle(ICall call)
at NSubstitute.Core.CallRouter.Route(ICall call)
at NSubstitute.Proxies.CastleDynamicProxy.CastleForwardingInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at NSubstitute.Proxies.CastleDynamicProxy.ProxyIdInterceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.ObjectProxy_2.Log[TState](LogLevel logLevel, EventId eventId, TState state, Exception exception, Func`3 formatter)
at Rm.Combo.App.Tests.VirtualParcels.Services.StockServiceTest.TestNSubstituteAgain() in C:\Users\eperret\Desktop\combo\api\Rm.Combo.App.Tests\VirtualParcels\Services\StockServiceTest.cs:line 99
// 1] Ok the call I am making is that ext. method below:
public static void LogCritical(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Critical, exception, message, args);
}
// 2] Which forwards to that other ext. method below:
public static void Log(this ILogger logger, LogLevel logLevel, Exception exception, string message, params object[] args)
{
logger.Log(logLevel, 0, exception, message, args);
}
// 3] and... then here (still an ext. method tho):
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, string message, params object[] args)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
logger.Log(logLevel, eventId, new FormattedLogValues(message, args), exception, _messageFormatter);
}
// 4] to finally end up with this "direct" interface method:
void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter);
// Note: which is supposedly what I am gonna ask NSubstitute to check against, right? (since we cannot use NSubstitute `Received` method on ext. method, it has to be the corresponding instance method)
注意:FormattedLogValues
是一个 internal struct
所以我真的不得不使用 Arg.Any<object>
。
当我检查实际收到的呼叫时,我看到记录器有一个呼叫,但我不太确定它与我所断言的有何不同。就像我在调试单元测试时在 var re = logger.ReceivedCalls();
之类的东西上设置断点:
我真的不知道什么不被视为 NSubstitute Received()
断言的正确参数。
有什么想法吗?
如果参数匹配非常困难,我会使用 ReceivedWithAnyArgs()
(https://nsubstitute.github.io/help/received-calls/)。如果事情很简单,NSubstitute 很好。如果它变得太复杂,我倾向于只创建接口的测试版本(例如 ILogger
),并从那里的调用中注册我感兴趣的参数,并检查那些。
我最终使用 Moq 进行了特定类型的测试:
// Defining an extension method
public static void IsLogReceived(this IMock<ILogger<StockService>>logger, LogLevel level, int count = 1) =>
logger.Verify(x =>
x.Log(level,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
Times.Exactly(count));
// [...]
// and using the ext. method like that:
[Fact]
public void TestNSubstituteAgain()
{
var loggerMock = new Mock<ILogger<StockService>>();
var logger = loggerMock.Object;
logger.LogCritical(new Exception(), "Hey lads!");
loggerMock.IsLogReceived(LogLevel.Critical);
}
它不是那么准确,但取决于方法范围内发生的事情,可以完成工作。
话虽这么说,但我承认最准确的实现方式是声明一个包装类型。
对我有点帮助的资源: