Environment: .NET 6.0
Create a console application, then create Foo.cs and fill in it with the followin code:
using Microsoft.Extensions.Logging;
namespace MyLog;
public class Foo
{
private ILogger<Foo> _logger;
public Foo(ILogger<Foo> logger)
{
_logger = logger;
}
public void PrintMessage()
{
_logger.LogInformation("Hello World!");
}
}
Create an NUnit project and create a UnitTest.cs with the following content:
using Moq;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using MyLog;
namespace MyTest;
public class Tests
{
[Test]
public void Test1()
{
List<string> logMessages = new List<string>();
var mockLogger = new Mock<ILogger<Foo>>();
mockLogger.Setup(m => m.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()!
)).Callback(new InvocationAction(invocation => {
var logLevel = (LogLevel)invocation.Arguments[0];
var eventId = (EventId)invocation.Arguments[1];
var state = invocation.Arguments[2];
var exception = (Exception)invocation.Arguments[3];
var formatter = invocation.Arguments[4];
var invokeMethod = formatter.GetType().GetMethod("Invoke");
var logMessage = invokeMethod!.Invoke(formatter, new[] { state, exception });
logMessages.Add((string)logMessage!);
}));
Foo foo = new Foo(mockLogger.Object);
foo.PrintMessage();
Assert.That("Hello World!", Is.EqualTo(logMessages[0]));
}
}
Finally, execute the test and it will pass successfully. All logs will be stored in logMessages, you can manipulate it for unit testing.
Top comments (2)
Why are you unit testing the logger? Isn't that a little redundant?
Yes, my colleagues suggested that counting the calling times of a specific function is better, so I didn't mock ILogger any more. But I think this technique is still worth writing down.