首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >无法测试用ILogger<T>接收到的NSubstitute

无法测试用ILogger<T>接收到的NSubstitute
EN

Stack Overflow用户
提问于 2020-03-14 15:53:45
回答 2查看 2K关注 0票数 3

我有一个.Net核心3应用程序,并试图用我的方法测试对ILogger的调用:

代码语言:javascript
运行
复制
public class MyClass
{
    private readonly ILogger<MyClass> _logger;

    public MyClass(ILogger<MyClass> logger)
    {
        _logger = logger;
    }

    public void MyMethod(string message)
    {
        _logger.LogError(message);
    }
}

在SO和博客上找到答案之后,我知道我必须针对接口方法而不是扩展方法进行测试,所以我进行了以下测试:

代码语言:javascript
运行
复制
[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>>());
}

但是,这是不起作用的,我得到了这个错误:

代码语言:javascript
运行
复制
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

更新:--我尝试了与Arg.Any<>()的匹配,并得到了相同的结果:

代码语言:javascript
运行
复制
logger.Received(1).Log(
    Arg.Any<LogLevel>(),
    Arg.Any<EventId>(),
    Arg.Any<object>(),
    Arg.Any<Exception>(),
    Arg.Any<Func<object, Exception, string>>());

更新2: --我使用进行了相同的测试,得到了相同的结果:

代码语言:javascript
运行
复制
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);

结果:

代码语言:javascript
运行
复制
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
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-03-16 12:37:36

用ILogger核心3.*测试.NET调用的主要问题是,FormattedLogValues被更改为内部,这会使事情复杂化。

Moq的解决方法是使用It.IsAnyType

代码语言:javascript
运行
复制
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方法,您可以迭代该方法并进行自己的调用检查。

代码语言:javascript
运行
复制
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));
    }
}

作为一种解决方法,它还不错,可以很容易地打包到扩展方法中。

FormattedLogValues实现了IReadOnlyList<KeyValuePair<string, object>>。此列表中的最后一项是您指定的原始消息。

工作样品

票数 11
EN

Stack Overflow用户

发布于 2022-04-08 10:31:14

我尝试将Logger封装在适配器上,更像是一个代理类。您可以模拟适配器接口,这将返回调用了什么日志函数。

代码语言:javascript
运行
复制
 public class LoggerAdapter<TType> : ILoggerAdapter<TType>
    {
        private readonly ILogger<TType> _logger;

        public LoggerAdapter(ILogger<TType> logger)
        {
            _logger = logger;
        }
        public void LogInformation(string message, params object[] args)
        {
            _logger.LogInformation(message,args);
        }

        public void LogError(string message, params object[] args)
        {
            _logger.LogError(message, args);
        }
    }
代码语言:javascript
运行
复制
 private readonly ILoggerAdapter<PosUpdateService> _logger = Substitute.For<ILoggerAdapter<PosUpdateService>>();
代码语言:javascript
运行
复制
_logger.Received(1).LogInformation("User with Id {id} was fetched in {0} milliseconds", 
               Arg.Is(Id),
               Arg.Any<long>());
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60684578

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档