Search code examples
mockingxunitsignalr-hubserilogasp.net-core-signalr

Serilog ILogger.ForContext throwing NullReferenceException in XUnit Mock


I am having an issue when trying to unit test signalr components using Mock. Here is where the issue occurs

 _logger.LogInformation($"Registering a Station with id: {Id}" +
            $" with status: {Status}" +
            $"{(!string.IsNullOrEmpty(CommandId) ? $", with command: {CommandId}" : "")}", 
            LoggingConstants.Component.MessageHub,
            LoggingConstants.Class.Workstation, 
            !string.IsNullOrEmpty(AppointmentId) ? 
                AppointmentId : LoggingConstants.NoAppointmentId,
            LoggingConstants.NoConfirmationNumber);

LogInformation is defined as

logger.ForContext("Component", (object) component, false).ForContext("Class", (object) @class, 
false).ForContext("AppointmentId", (object) appointmentId, false).ForContext("ConfirmationNumber", 
(object) confirmationNumber, false).Information(message);

In the Xunit Unit test class, it is being used as

public Mock<ILogger> MockLogger { get; set; }
MockLogger = new Mock<ILogger>();
Workstation = new Workstation(MockLogger.Object);

When the unit test is run, once it hits that _logger.LogInformation() message, it throws a

"System.NullReferenceException : Object reference not set to an instance of an object.
at LogInformation(ILogger logger, String message, String component, String class, String 
appointmentId, String confirmationNumber)"

To verify that it is being thrown because of the ForContext, this test was used

_logger.Information("a") -> Works
_logger.ForContext("a", "a").Information("a") -> Exception is thrown

Solution

  • That's expected... You are creating a mock of ILogger without setting up what ForContext should return, and then trying to use the return of ForContext which is obviously null.

    You have to call Setup on your mock to configure ForContext to return a valid ILogger.

    e.g.

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

    However, it looks you are not testing anything about logging and is just creating a mock of ILogger to satisfy the dependency of the class being tested. In that case, you don't have to create a mock at all... You can simply use Logger.None which is a SilentLogger that does nothing (it doesn't log nor throws errors).

    e.g.

    Workstation = new Workstation(Logger.None);