【问题标题】:Unable to Mock ILogger in Nunit无法在 Nunit 中模拟 ILogger
【发布时间】:2020-07-07 12:02:41
【问题描述】:

Net core 和 NUnit 测试用例。我的控制器如下。

public class MyController : ControllerBase
    {
      public MyController(ILogger<MyController > logger)
        {
            this.Logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
    private readonly ILogger<MyController > Logger;

    public async Task<ActionResult> GetList()
    {
      this.Logger.LogInformation($"MyList: Some results came");
    }
 }

然后在我的单元测试用例下面。

 public class MyControllerTests
 {
   private Mock<ILogger<MyController>> Logger = new Mock<ILogger<MyController>>();
   internal MyController myController;
   public MyControllerTests()
        {
            this.myController= new MyController(this.Logger.Object);
        }
    [Test]
        public async Task ListTest()
        {
           Logger.Verify(
               x => x.Log(
                   LogLevel.Information,
                   It.IsAny<EventId>(),
                   It.Is<It.IsAnyType>((o, t) => string.Equals("MyList: Some results came", o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
                   It.IsAny<Exception>(),
                   (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
         //rest of the code but i am getting error in above line
        }
 }

以上代码报错

Moq.MockException : ILogger.Log(LogLevel.Information, 0, ListTest : 成功获取结果, null, Func) 调用失败,模拟行为 Strict。 模拟上的所有调用都必须有相应的设置。

谁能帮我解决这个问题。任何帮助将不胜感激。谢谢

【问题讨论】:

  • 试试new Mock&lt;ILogger&lt;MyController&gt;&gt;(MockBehavior.Loose);
  • 你要测试哪个逻辑?除了记录器验证之外什么都没有
  • 正如错误消息所说,您需要在模拟对象上设置方法调用,然后才能验证它们。此外,测试代码没有任何调用 Logger 中任何方法的内容。

标签: c# asp.net-core nunit ilogger


【解决方案1】:

你很亲密。这是关于此的article。以下是我提出的两个有效验证:

        _logTest.Process();
        _loggerMock.Verify(l => l.Log(
            LogLevel.Information,
            It.IsAny<EventId>(),
            It.IsAny<It.IsAnyType>(),
            It.IsAny<Exception>(),
            (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Exactly(1));

这个版本可以让你更具体:

        _loggerMock.Verify
        (
            l => l.Log
            (
                //Check the severity level
                LogLevel.Error,
                //This may or may not be relevant to your scenario
                It.IsAny<EventId>(),
                //This is the magical Moq code that exposes internal log processing from the extension methods
                It.Is<It.IsAnyType>((state, t) =>
                    //This confirms that the correct log message was sent to the logger. {OriginalFormat} should match the value passed to the logger
                    //Note: messages should be retrieved from a service that will probably store the strings in a resource file
                    CheckValue(state, "MyList: Some results came", "{OriginalFormat}") &&
                    //This confirms that an argument with a key of "recordId" was sent with the correct value
                    //In Application Insights, this will turn up in Custom Dimensions
                    CheckValue(state, recordId, nameof(recordId))
            ),
            //Confirm the exception type
            It.IsAny<ArgumentNullException>(),
            //Accept any valid Func here. The Func is specified by the extension methods
            (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
            //Make sure the message was logged the correct number of times
            Times.Exactly(1)
        );

【讨论】:

    【解决方案2】:

    一般来说,您应该在测试和运行时都依赖 DI。以下库包含您可以在测试中使用的测试记录器:https://www.nuget.org/packages/com.github.akovac35.Logging.Testing/

    使用示例可在此处获得:https://github.com/akovac35/Logging.Samples

    免责声明:我是以上内容的作者。

    基本上你会进行如下操作:

    默认使用 NullLogger:

    public class MyController : ControllerBase
    {        
        private ILogger _logger = NullLogger.Instance;
        
        protected MyController(ILogger<MyController> logger = null)
        {
            if (logger != null) _logger = logger;
        }
    }
    

    现在连接测试:

    using com.github.akovac35.Logging.Testing;
    using Microsoft.Extensions.DependencyInjection;
    using NUnit.Framework;
    using Shared.Mocks;
    using System;
    
    namespace TestApp
    {
        [TestFixture]
        public class TestLoggingExamples
        {
            [OneTimeSetUp]
            public void OneTimeSetUp()
            {
                customOnWrite = writeContext => {
                    Console.WriteLine(writeContext);
                };
    
                customOnBeginScope = scopeContext => {
                    Console.WriteLine(scopeContext);
                };
    
                serviceCollection = new ServiceCollection();
                serviceCollection.AddTransient(typeof(MyController));
    
                // Register TestLogger using extension method
                serviceCollection.AddTestLogger(onWrite: customOnWrite, onBeginScope: customOnBeginScope);
            }
    
            private IServiceCollection serviceCollection;
    
            private Action<WriteContext> customOnWrite;
            private Action<ScopeContext> customOnBeginScope;
    
            [Test]
            public void Test_WithLoggingToTestConsole_Works()
            {
                // The service provider should be defined on per-test level or logger writes will accumulate and may result in OOM - clean them with testSink.Clear()
                var serviceProvider = serviceCollection.BuildServiceProvider();
                var controller = serviceProvider.GetRequiredService<MyController>();
                controller.Invoke();
    
                var testSink = serviceProvider.GetRequiredService<ITestSink>();
    
                Assert.IsTrue(testSink.Writes.Count > 0);
                Assert.IsTrue(testSink.Scopes.Count > 0);
            }
        }
    }
    

    testSink.Writes 包含可以断言特定日志消息的 WriteContext 对象:

    using Microsoft.Extensions.Logging;
    using System;
    
    namespace com.github.akovac35.Logging.Testing
    {
        [System.Diagnostics.DebuggerDisplay("{ToString()}")]
        public class WriteContext
        {
            public LogLevel LogLevel { get; set; }
    
            public EventId EventId { get; set; }
    
            public object State { get; set; }
    
            public Exception Exception { get; set; }
    
            public Func<object, Exception, string> Formatter { get; set; }
    
            public ScopeContext Scope { get; set; }
    
            public ILogger Logger { get; set; }
    
            public DateTime Timestamp { get; set; }
    
            public int ThreadId { get; set; }
    
            public virtual string Message
            {
                get
                {
                    return Formatter(State, Exception);
                }
            }
    
            public override string ToString()
            {
                return $"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] {ThreadId} {LogLevel} EventId: {EventId}{(Scope != null ? $" Scope: <{Scope}>" : "")} Message: {Message}{(Exception != null ? $"{Environment.NewLine}{Exception}" : "")}";
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-03-13
      • 2020-11-27
      • 2021-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多