无法测试 ILogger<T>收到 NSubstitute [英] Cannot test ILogger<T> Received with NSubstitute
问题描述
我有一个 .Net Core 3 应用程序,正在尝试在我的方法中测试对 ILogger 的调用:
I have a .Net Core 3 application and am trying to test calls to ILogger in my method:
public class MyClass
{
private readonly ILogger<MyClass> _logger;
public MyClass(ILogger<MyClass> logger)
{
_logger = logger;
}
public void MyMethod(string message)
{
_logger.LogError(message);
}
}
在 SO 和博客上找到答案后,我知道我必须针对接口方法进行测试,而不是针对扩展方法进行测试,因此我进行了此测试:
Having found answers here on SO and on blogs, I know that I have to test against the interface method, not the extension method, so I have this test:
[TestMethod]
public void MyMethodTest()
{
// Arrange
var logger = Substitute.For<ILogger<MyClass>>();
var myClass = new MyClass(logger);
var message = "a message";
// Act
myClass.MyMethod(message);
// Assert
logger.Received(1).Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString() == message),
null,
Arg.Any<Func<object, Exception, string>>());
}
但是,这不起作用,我收到此错误:
However, this isn't working and I get this error:
Test method MyLibrary.Tests.MyClassTests.MyMethodTest threw exception:
NSubstitute.Exceptions.ReceivedCallsException: Expected to receive exactly 1 call matching:
Log<Object>(Error, any EventId, o => (o.ToString() == value(MyLibrary.Tests.MyClassTests+<>c__DisplayClass0_0).message), <null>, 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.Log[TState](LogLevel logLevel, EventId eventId, TState state, Exception exception, Func`3 formatter)
at MyLibrary.Tests.MyClassTests.MyMethodTest() in D:\Source\Scratch\MyLibrary\MyLibrary.Tests\MyClassTests.cs:line 25
我做错了什么?
netcoreapp3.0/Microsoft.Extensions.Logging 3.1.2/NSubstitute 4.2.1
netcoreapp3.0 / Microsoft.Extensions.Logging 3.1.2 / NSubstitute 4.2.1
更新:我已经尝试与 Arg.Any<>()
匹配并得到相同的结果:
UPDATE: I have tried the match with Arg.Any<>()
and get the same result:
logger.Received(1).Log(
Arg.Any<LogLevel>(),
Arg.Any<EventId>(),
Arg.Any<object>(),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>());
更新 2: 我已经尝试使用 Moq 进行相同的测试并得到相同的结果:
UPDATE 2: I have tried the same test using Moq and get the same result:
logger.Verify(l => l.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.Is<object>(o => o.ToString() == message),
null,
It.IsAny<Func<object, Exception, string>>()),
Times.Once);
结果:
Test method MyLibrary.Tests.Moq.MyClassTests.MyMethodTest threw exception:
Moq.MockException:
Expected invocation on the mock once, but was 0 times: l => l.Log<object>(LogLevel.Error, It.IsAny<EventId>(), It.Is<object>(o => o.ToString() == "a message"), null, It.IsAny<Func<object, Exception, string>>())
Performed invocations:
Mock<ILogger<MyClass>:1> (l):
ILogger.Log<FormattedLogValues>(LogLevel.Error, 0, a message, null, Func<FormattedLogValues, Exception, string>)
at Moq.Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
at Moq.Mock`1.Verify(Expression`1 expression, Times times)
at Moq.Mock`1.Verify(Expression`1 expression, Func`1 times)
at MyLibrary.Tests.Moq.MyClassTests.MyMethodTest() in D:\Source\Scratch\MyLibrary\MyLibrary.Tests.Moq\MyClassTests.cs:line 25
推荐答案
使用 .NET Core 3.* 对 ILogger 调用进行单元测试的主要问题是 FormattedLogValues
已更改为内部,这使事情变得复杂.
The main issue unit testing ILogger invocations with .NET Core 3.* is that FormattedLogValues
was changed to internal, it complicates things.
Moq 解决方法是使用 It.IsAnyType
:
The Moq workaround is to use It.IsAnyType
:
public class TestsUsingMoq
{
[Test]
public void MyMethod_String_LogsError()
{
// Arrange
var logger = Mock.Of<ILogger<MyClass>>();
var myClass = new MyClass(logger);
var message = "a message";
// Act
myClass.MyMethod(message);
//Assert
Mock.Get(logger)
.Verify(l => l.Log(LogLevel.Error,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => ((IReadOnlyList<KeyValuePair<string, object>>) o).Last().Value.ToString().Equals(message)),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>) It.IsAny<object>()),
Times.Once);
}
}
据我所知,
NSubstitute 目前没有 It.IsAnyType
等效项,这在尝试使用 Received
方法时会出现问题.但是,有一种解决方法,因为它确实提供了一个 ReceivedCalls
方法,您可以对其进行迭代并进行自己的调用检查.
NSubstitute doesn't have an It.IsAnyType
equivalent at the moment as far as I am aware, which presents an issue when trying to use the Received
method. There is a workaround however as it does provide a ReceivedCalls
method which you can iterate over and do you own invocation check.
public class TestsUsingNSubstitute
{
[Test]
public void MyMethod_String_LogsError()
{
// Arrange
var logger = Substitute.For<ILogger<MyClass>>();
var myClass = new MyClass(logger);
var message = "a message";
// Act
myClass.MyMethod(message);
//Assert
Assert.That(logger.ReceivedCalls()
.Select(call => call.GetArguments())
.Count(callArguments => ((LogLevel) callArguments[0]).Equals(LogLevel.Error) &&
((IReadOnlyList<KeyValuePair<string, object>>) callArguments[2]).Last().Value.ToString().Equals(message)),
Is.EqualTo(1));
}
}
作为一种解决方法,它不是一个坏方法,并且可以很容易地捆绑到一个扩展方法中.
As a workaround, it's not a bad one, and could be easily bundled up into an extension method.
FormattedLogValues
实现了 IReadOnlyList
.此列表中的最后一项是您指定的原始邮件.
FormattedLogValues
implements IReadOnlyList<KeyValuePair<string, object>>
. The last item in this list is the original message that you specified.
这篇关于无法测试 ILogger<T>收到 NSubstitute的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!