无法在 Nunit 中模拟 ILogger
Unable to Mock ILogger in Nunit
Net 核心和 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
: Successfully Fetched the Results, null, Func<FormattedLogValues,
Exception, string>) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.
谁能帮我解决这个问题。任何帮助将不胜感激。谢谢
你非常接近。这是关于此的 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)
);
一般来说,您应该在测试和运行时依赖 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}" : "")}";
}
}
}
Net 核心和 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 : Successfully Fetched the Results, null, Func<FormattedLogValues, Exception, string>) invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.
谁能帮我解决这个问题。任何帮助将不胜感激。谢谢
你非常接近。这是关于此的 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)
);
一般来说,您应该在测试和运行时依赖 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}" : "")}";
}
}
}