Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I provide ILogger<T> in my unit tests of .NET Core code?

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.

like image 214
Tomas Aschan Avatar asked Aug 07 '17 09:08

Tomas Aschan


People also ask

How do you mock an ILogger?

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).

What is the use of ILogger in .NET Core?

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.

How do I inject a logger in .NET Core?

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.

How to use ILogger methods for unit testing?

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.

Is it possible to write code without unit testing?

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.

How to create logs using ILogger<T>?

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.

How to create an ILogger<t> object in a non-host console application?

(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.


6 Answers

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)

like image 156
Christoph Lütjen Avatar answered Oct 18 '22 23:10

Christoph Lütjen


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);
}
like image 31
Adrian Toman Avatar answered Oct 18 '22 23:10

Adrian Toman


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<>));
like image 5
Mustafa Çağlar Aras Avatar answered Oct 18 '22 22:10

Mustafa Çağlar Aras


You have two options:

  1. Create empty implementation of ILogger<Foo> by hand and pass an instance of it to ctor.
  2. Create same empty implementation on the fly using some mocking framework like Moq, NSubstitute, etc.
like image 3
Oleksandr Kobylianskyi Avatar answered Oct 19 '22 00:10

Oleksandr Kobylianskyi


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>()
like image 2
adospace Avatar answered Oct 18 '22 22:10

adospace


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.

like image 1
DavidG Avatar answered Oct 19 '22 00:10

DavidG