Given a class with a constructor signature of
public Foo(ILogger<Foo> logger) {
// ...
}
that I want to test, I need some way to provide an ILogger<Foo>
in the test. It's been asked before, but the only answer then was to set up a full-blown service registry, configure logging and resolve the logger from there. This seems very overkill to me.
Is there a simple way to provide an implementation of ILogger<T>
for testing purposes?
Note: it doesn't have to actually log anything - just not blow up when the subject under test tries to log something.
To mock an ILogger<T> object, we can use Moq library to instantiate a new Mock<ILogger<T>>() object. Make sure you have installed the latest Moq package (v4. 15.1 as of Nov. 16, 2020, which contains an update to support “nested” type matchers).
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.
So, go to the Startup. cs file and add the ILoggerFactory parameter in the Configure() method. Then, call the AddFile() extension method to add Serillog file provider, as shown below. ASP.NET Core dependency injection will automatically pass an instance of the LoggerFactory for this parameter.
Here is sample code which uses ILogger methods for logging purpose. For the above code, we shall be writing Unit testing using the code-first approach. As a first principle, you unit test code that you own and Mock everything else which you don’t own. The second important thing is Unit Test should not log to a file or console or any other location.
When code is tightly coupled, it can be difficult to unit test. Without creating unit tests for the code that you're writing, coupling may be less apparent. Writing tests for your code will naturally decouple your code, because it would be more difficult to test otherwise.
In order to create logs, we need to use an ILogger<T> object, which is readily accessible in our applications. (1) In a web application or a hosted service, we can get an ILogger<T> object from the native .NET Core dependency injection (DI) system.
(2) In a non-host console application, we can use the LoggerFactory to create an ILogger<T> object. Because ILogger<T> objects are frequently used in controllers and service classes, we cannot avoid them in unit tests.
Starting from dotnet core 2.0 there's a generic NullLogger<T> class available:
var foo = new Foo(NullLogger<Foo>.Instance);
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nulllogger-1?view=aspnetcore-2.1 (docs) https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/NullLoggerOfT.cs (source)
Or if you need it as part of your services:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nullloggerfactory?view=aspnetcore-2.1 (docs)
You can create an instance of ILogger<Foo>
using NullLoggerFactory
as the factory.
Consider the following controller:
public abstract class Foo: Controller
{
public Foo(ILogger<Foo> logger) {
Logger = logger;
}
public ILogger Logger { get; private set; }
}
A sample unit test could be:
[TestMethod]
public void FooConstructorUnitTest()
{
// Arrange
ILogger<FooController> logger = new Logger<FooController>(new NullLoggerFactory());
// Act
FooController target = new FooController(logger);
// Assert
Assert.AreSame(logger, target.Logger);
}
If you use generic logger (ILogger<>) in your classes those instances are generated from IServiceProvider you should register generic NullLogger<> on service provider as below. Not important what you use generic type T in ILogger<>
services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
You have two options:
ILogger<Foo>
by hand and pass an instance of it to ctor.You could inject ILoggerFactory
instead and then create the logger
public Foo(ILoggerFactory loggerFactory) {
logger = loggerFactory.CreateLogger<Foo>();
// ...
}
At startup you need to add the NullLoggerFactory
service of course:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>()
From the docs for ILogger<T>
(emphasis mine):
A generic interface for logging where the category name is derived from the specified TCategoryName type name. Generally used to enable activation of a named ILogger from dependency injection.
So one option would be to change the implementation of the Foo
method to take a plain ILogger
and use the NullLogger
implementation.
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