1

I have my registration and constructor setup to use logging via Microsoft.Extensions.Logging as follows,

public MyService(ILogger<MyService> logger) {}

What I'd like to do, though, is use services.AddSingleton() in my Startup.cs to avoid using constructor injection. The reason is that it will otherwise require modifying all unit tests to Mock the logger when testing the service.
I believe there's a way to register the specific instance of ILogger in the ServiceCollection, but I cannot sort out the syntax required.

Thanks!

McArthey
  • 1,614
  • 30
  • 62
  • `ILogger` must never be registered as `Singleton` though. – Dai Feb 12 '22 at 17:57
  • 2
    "The reason is that it will otherwise require modifying all unit tests to Mock the logger when testing the service." - but *that's the point* - if you don't care about what gets logged in a test then use `Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory`. – Dai Feb 12 '22 at 17:58
  • @Dai Thanks so much, I wasn't aware of that abstraction. That solves my problem! – McArthey Feb 12 '22 at 18:06

1 Answers1

5
  • In unit tests you should not be using a DI container.
    • If you were, then you're writing an integration test, not a unit test.
  • In unit tests, generally speaking, the arrange part of the test should set-up your SUT by directly invoking SUT's constructor and passing-in test/mock/stub versions of its dependencies.
  • In unit tests, ideally you should supply an entire testing-implementation of the entire Microsoft.Extensions.Logging.ILoggerProvider interface (and related classes) which will allow you to assert that specific log output messages were written, or to redirect ILogger and ILogger<T> output to your test's own output.
  • Alternatively, if your tests aren't concerned with asserting ILogger usage and/or if you don't want your SUT's own log output written to your test output, then use NullLoggerFactory or NullLogger<T>.Instance in your arrange section to create a stub ILogger<T> that doesn't do anything.

Like this:

using Microsoft.Extensions.Logging.Abstractions;

// [...]

[Fact]
public void MyService_should_do_something()
{
    // Arrange:

    IArbitraryDependency dep         = new StubArbitraryDependency();
#if FULL_MOON_TONIGHT
    ILogger<MyService>   nullLogger  = NullLoggerFactory.Instance.CreateLogger<MyService>();
#else
    ILogger<MyService>   nullLogger  = NullLogger<MyService>.Instance();
#endif

    MyService systemUnderTest = new MyService(
        arbitraryDependency: dep,
        logger             : nullLogger
    );

    // Act:

    systemUnderTest.DoSomething();

    // Assert:
    // [...]
}
Dai
  • 141,631
  • 28
  • 261
  • 374
  • Yep, that worked perfectly, thanks! My setup looks like this now and is just what I needed, `var service = new MyService(_mockDate.Object, NullLogger.Instance);` – McArthey Feb 12 '22 at 18:10
  • Ooooh, I didn't know you could skip `NullLoggerFactory` and just use `NullLogger.Instance` directly - I'll update my answer. – Dai Feb 12 '22 at 18:38