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:
(I am using very "wordy" values to clarify what is what (a log-msg/"state" vs what the formatter does). (Your code probably won't be "wordy" like mine).
string logMsgAkaTheState = "Here_Is_The_Log_Message_Aka_The_StateArgument";
Func<object, Exception, string> formatterFunc = (theObj, theException) => "*formatter-start*" + theObj + "*formatter-middle*" + (null == theException ? "theExceptionIsNull" : theException.Message) + "*formatter-end*";
logger.Log(LogLevel.Information, ushort.MaxValue, logMsgAkaTheState, null, formatterFunc);
or for one that is an exception
catch (Exception ex)
{
string logMsgAkaTheState = "Here_Is_Another_Log_Message_Aka_The_StateArgument";
Func<object, Exception, string> errorFormatterFunc = (theObj, theException) => "*formatter-start*" + theObj + "*formatter-middle*" + (null == theException ? "theExceptionIsNull" : theException.Message) + "*formatter-end*";
this.logger.Log(LogLevel.Error, ushort.MaxValue, null, ex, errorFormatterFunc);
}
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()
{
string logMsgAkaTheState = "Here_Is_The_Log_Message_Aka_The_StateArgument";
Func<object, Exception, string> formatterFunc = (theObj, theException) => "*formatter-start*" + theObj + "*formatter-middle*" + (null == theException ? "theExceptionIsNull" : theException.Message) + "*formatter-end*";
logger.Log(LogLevel.Information, ushort.MaxValue, logMsgAkaTheState, null, formatterFunc);
int div = 0;
try
{
int x = 1 / div;
}
catch (Exception ex)
{
string logMsgAkaTheState = "Here_Is_Another_The_Log_Message_Aka_The_StateArgument";
Func<object, Exception, string> errorFormatterFunc = (theObj, theException) => "*formatter-start*" + theObj + "*formatter-middle*" + (null == theException ? "theExceptionIsNull" : theException.Message) + "*formatter-end*";
this.logger.Log(LogLevel.Error, ushort.MaxValue, null, ex, errorFormatterFunc);
}
}
}