This expands on Muk's answer.
My addition is "ILoggerFactory"..and how you "code up" the actually logging inside your real class.
First, unit test code:
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
private Mock<ILoggerFactory> GetDefaultILoggerFactoryMock()
{
Mock<ILoggerFactory> returnMock = new Mock<ILoggerFactory>(MockBehavior.Loose);
returnMock.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(
() =>
this.GetDefaultILoggerMock<MyConcreteClassThatUsesILoggerFactoryInItsConstructor>().Object);
return returnMock;
}
private Mock<ILogger<T>> GetDefaultILoggerMock<T>()
{
Mock<ILogger<T>> returnMock = new Mock<ILogger<T>>(MockBehavior.Strict);
returnMock.Setup(
m => m.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<object>(),
It.IsAny<Exception>(),
It.IsAny<Func<object, Exception, string>>())).Callback(
(LogLevel ll, EventId eid, object obj1, Exception ex, Func<object, Exception, string> func) =>
{
Console.WriteLine(func.Invoke(obj1, ex));
}).Verifiable();
returnMock.Setup(m => m.IsEnabled(It.IsAny<LogLevel>())).Returns(false);
return returnMock;
}
Now, in your actual class (here I call it 'MyConcreteClassThatUsesILoggerFactoryInItsConstructor')..you need to use the NON EXTENSION METHOD of making log calls. (All the helper methods like LogInformation are static extension methods overloads)
So like this:
Func<object, Exception, string> logMsgFunc = (a, b) => "MyDebugMessageOne";
this.logger.Log(LogLevel.Debug, ushort.MaxValue, null, null, logMsgFunc);
or for one that is an exception
catch (Exception ex)
{
Func<object, Exception, string> logMsgFunc = (a, b) => "MyErrorContextMessageTwo";
this.logger.Log(LogLevel.Error, ushort.MaxValue, null, ex, logMsgFunc);
}
If you do both of those things, especially the second part, you can Mock the ILoggerFactory / ILogger
Here is the germane parts of the class/constructor:
using Microsoft.Extensions.Logging;
public class MyConcreteClassThatUsesILoggerFactoryInItsConstructor : IMyConcreteClassThatUsesILoggerFactoryInItsConstructor
{
public const string ErrorMsgILoggerFactoryIsNull = "ILoggerFactory is null";
private readonly ILogger<MyConcreteClassThatUsesILoggerFactoryInItsConstructor> logger;
public MyConcreteClassThatUsesILoggerFactoryInItsConstructor(
ILoggerFactory loggerFactory)
{
if (null == loggerFactory)
{
throw new ArgumentNullException(ErrorMsgILoggerFactoryIsNull, (Exception)null);
}
this.logger = loggerFactory.CreateLogger<MyConcreteClassThatUsesILoggerFactoryInItsConstructor>();
}
public void DoSomething()
{
Func<object, Exception, string> logMsgFunc = (a, b) => "DoSomething Started";
this.logger.Log(LogLevel.Debug, ushort.MaxValue, null, null, logMsgFunc);
int div = 0;
try
{
int x = 1 / div;
}
catch (Exception ex)
{
Func<object, Exception, string> errorMsgFunc = (a, b) => string.Format("MyErrorContextMessageTwo (div='{0}')", div);
this.logger.Log(LogLevel.Error, ushort.MaxValue, null, ex, errorMsgFunc);
}
}
}