Search code examples
c#unit-testing.net-coreserilog

How to unit test Serilog calls when using ForContext


I'm using the ForContext() method of Serilog to add custom properties to the log event as per the general guidance

The problem with this is that from what I can see in the source code, calling ForContext() does this:

    ILogger ForContext(string propertyName, object? value, bool destructureObjects = false)
        => new LoggerConfiguration()
            .MinimumLevel.Is(LevelAlias.Minimum)
            .WriteTo.Logger(this)
            .CreateLogger()
            .ForContext(propertyName, value, destructureObjects)
    ;

which returns a new object, so when I chain these calls and I try to write unit tests to assert that ForContext was called for each property and the appropriate log method was called it always fails. For example:

public class MyService
{
  ILogger _logger;  

  public MyService (ILogger log)
  {
    _logger = log
  }


  public void DoStuff()
  {
    _logger
        .ForContext("One", 1)
        .ForContext("Two", 2)
        .Warning("Not happy");
  }
}

and the following example test:

public class MyServiceTest
{
  Mock<ILogger> _loggerMock = new();  
  MyService sut;

  public MyServiceTest(ILogger log)
  {
   _loggerMock.Setup(x => x.Warning(It.IsAny<string>()).Verifiable();
    sut = new MyService(_loggerMock.Object);
  }


  public void DoStuffShouldLogWarning()
  {
     sut.DoStuff();
    _loggerMock.Verify(x => x.Warning(It.IsAny<string>()), times.once);
  }
}

I get that Warning was never called but that ForContext("One", 1) was called once and then Write() was called....which is basically true.

How can I do this in a way that allows me to make sure some other developer (or me) didn't just random remove a property from the log, or use the wrong value, or change the log level as part of a code change.


Solution

  • Off the back of the comment from @Jeanot Zubler (thanks for pointing me in the right direction) based on my original example I solve it like this:

    _mockLogger.Setup(x => x.ForContext(It.IsAny<string>(), It.IsAny<object>())).Returns(_mockLogger.Object);
    

    you can then do this:

    // Verify the for context call
    _mockLogger.Verify(o => o.ForContext(It.IsAny<string>(), It.IsAny<int>()), Times.Once());
    // Verify the chained warning
    _loggerMock.Verify(x => x.Warning(It.IsAny<string>()), times.once);