Skip to content

Handling ILogger in your tests

Jon P Smith edited this page Mar 15, 2022 · 2 revisions

ILogger and ILogger<T> are common services that you might use in your code or in code from other libraries you might be using. If you want to test code that has ILogger / ILogger<T> in them, then you need a way to provide the correct logger type to make your code work. There are two approaches to handling ILogger / ILogger<T>, depending on whether you need dependency injection (DI) in your tests.

Manually providing a ILogger / ILogger<T>

The EfCore.TestSupport contains a MyLoggerProviderActionOut which is a ILoggerProvider. This logger provider will call an action every time a log is and returns a LogOutput class containing the various parts of the log it is linked to. Below is the code you need to turn the MyLoggerProviderActionOut into a ILogger or ILogger<T>.

NOTE: The MyLoggerProviderActionOut has a second parameter called logLevel, which defaults to LogLevel.Information. This means only logs with LogLevel.Information or higher will cause a call on the action. You can change the LogLevel by providing a different value.

Most logging uses the ILogger<T> type and here is you can MyLoggerProviderActionOut to create such a log, in this case a Ilogger<MyLoggerType>.

public List<LogOutput> Logs { get; } = new List<LogOutput>();
ILogger<MyLoggerType> logger = new LoggerFactory(
    new[] { new MyLoggerProviderActionOut(log => Logs.Add(log)) })
    .CreateLogger<MyLoggerType>();

If you want a non-generic ILogger, then you use the code below:

public List<LogOutput> Logs { get; } = new List<LogOutput>();
ILogger logger = new LoggerFactory(
     new[] { new MyLoggerProviderActionOut(Logs.Add) })
    .CreateLogger("category name");

Adding loggers to dependency injection (DI)

With complex code with lots of setup its sometimes useful to setup the code used in your Program file. If any of the code uses a ILogger / ILogger<T>, then your setup of the services will fail with a exception saying "Unable to resolve service..." (see TestDependencyInjectionFailIfNoLogger test in the TestMyLoggerProviderActionOut class) . You have two options to handle logging in DI.

You don't want to inspect any logs

The simple approach is to use AddLogging, which means any code that uses a ILogger<T> service will work, but you won't see the log output.

var services = new ServiceCollection();
services.AddTransient<MyService>(); //service that uses logging
services.AddLogging();
//... other DI registering left out
var serviceProvider = services.BuildServiceProvider();

NOTE: See the TestAddLoggerInDependencyInjection test method in the TestMyLoggerProviderActionOut class for a full test.

If you want to inspect the logs for a specific logger

Sometimes you may want to inspect the logs coming from a specific service. You can do this by registering a singleton logger against the service's logger type. In the code below shows how to do this.

var logs = new List<LogOutput>();
var services = new ServiceCollection();
services.AddTransient<MyService>(); //service that uses logging
services.AddSingleton<ILogger<MyService>>(x =>
    new LoggerFactory(
            new[] { new MyLoggerProviderActionOut(l => logs.Add(l)) })
        .CreateLogger<MyService>());
//... other DI registering left out
var serviceProvider = services.BuildServiceProvider();

NOTE: See the TestAddingLoggerInDependencyInjection test method in the TestMyLoggerProviderActionOut class for a full test.

My tests says that you can have both services.AddLogging() and registered services.AddSingleton<ILogger... together - you will get the logging from the specified logging type and any other logging will be discarded. See the TestAddingLoggerInDependencyInjectionWithAddLogging test method in the TestMyLoggerProviderActionOut class

Clone this wiki locally