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