ILoggerFactory is a factory interface that we can use to create instances of the ILogger type and register logging providers. It acts as a wrapper for all the logger providers registered to it and a logger it creates can write to all the logger providers at once.
ILogger<T> is derived from ILogger and adds no new functionality. If you're using dependency injection, an instance of ILogger<T> is usually injected into your type T . So, each time you have a constructor that takes an ILogger<T> , you are defining a “component” for your application.
The responsibility of the ILogger interface is to write a log message of a given log level and create logging scopes. The interface itself only exposes some generic log methods which are then used by “external” extension methods like LogInformation or LogError .
Just mock it as well as any other dependency:
var mock = new Mock<ILogger<BlogController>>();
ILogger<BlogController> logger = mock.Object;
//or use this short equivalent
logger = Mock.Of<ILogger<BlogController>>()
var controller = new BlogController(logger);
You probably will need to install Microsoft.Extensions.Logging.Abstractions
package to use ILogger<T>
.
Moreover you can create a real logger:
var serviceProvider = new ServiceCollection()
.AddLogging()
.BuildServiceProvider();
var factory = serviceProvider.GetService<ILoggerFactory>();
var logger = factory.CreateLogger<BlogController>();
Actually, I've found Microsoft.Extensions.Logging.Abstractions.NullLogger<>
which looks like a perfect solution. Install the package Microsoft.Extensions.Logging.Abstractions
, then follow the example to configure and use it:
using Microsoft.Extensions.Logging;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
...
}
using Microsoft.Extensions.Logging;
public class MyClass : IMyClass
{
public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";
private readonly ILogger<MyClass> logger;
public MyClass(ILoggerFactory loggerFactory)
{
if (null == loggerFactory)
{
throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
}
this.logger = loggerFactory.CreateLogger<MyClass>();
}
}
and unit test
//using Microsoft.VisualStudio.TestTools.UnitTesting;
//using Microsoft.Extensions.Logging;
[TestMethod]
public void SampleTest()
{
ILoggerFactory doesntDoMuch = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
IMyClass testItem = new MyClass(doesntDoMuch);
Assert.IsNotNull(testItem);
}
UPDATE (thanks @Gopal Krishnan for the comment):
With Moq >= 4.15.0 the following code is working (the cast is no longer needed):
loggerMock.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => string.Equals("Index page say hello", o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
Times.Once);
Previous version of the answer (for Moq < 4.15.0):
For .net core 3 answers that are using Moq
are no longer working due to a change described in the issue TState in ILogger.Log used to be object, now FormattedLogValues
Luckily stakx provided a nice workaround. So I'm posting it in hope it can save time for others (it took a while to figure the things out):
loggerMock.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((o, t) => string.Equals("Index page say hello", o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>) It.IsAny<object>()),
Times.Once);
Use a custom logger that uses ITestOutputHelper
(from xunit) to capture output and logs. The following is a small sample that only writes the state
to the output.
public class XunitLogger<T> : ILogger<T>, IDisposable
{
private ITestOutputHelper _output;
public XunitLogger(ITestOutputHelper output)
{
_output = output;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_output.WriteLine(state.ToString());
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state)
{
return this;
}
public void Dispose()
{
}
}
Use it in your unittests like
public class BlogControllerTest
{
private XunitLogger<BlogController> _logger;
public BlogControllerTest(ITestOutputHelper output){
_logger = new XunitLogger<BlogController>(output);
}
[Fact]
public void Index_ReturnAViewResult_WithAListOfBlog()
{
var mockRepo = new Mock<IDAO<Blog>>();
mockRepo.Setup(repo => repo.GetMany(null)).Returns(GetListBlog());
var controller = new BlogController(_logger,mockRepo.Object);
// rest
}
}
Adding my 2 cents, This is a helper extension method typically put in a static helper class:
static class MockHelper
{
public static ISetup<ILogger<T>> MockLog<T>(this Mock<ILogger<T>> logger, LogLevel level)
{
return logger.Setup(x => x.Log(level, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()));
}
private static Expression<Action<ILogger<T>>> Verify<T>(LogLevel level)
{
return x => x.Log(level, 0, It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>());
}
public static void Verify<T>(this Mock<ILogger<T>> mock, LogLevel level, Times times)
{
mock.Verify(Verify<T>(level), times);
}
}
Then, you use it like this:
//Arrange
var logger = new Mock<ILogger<YourClass>>();
logger.MockLog(LogLevel.Warning)
//Act
//Assert
logger.Verify(LogLevel.Warning, Times.Once());
And of course you can easily extend it to mock any expectation (i.e. expection, message, etc …)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With