0

Some of my unit tests are failing after updating Microsoft.Extensions.Logging.Abstractions from Version=2.0.0.0 to Version=3.1.1.0.

I have an ILogger mocked as so:

var loggerMock = new Mock<ILogger<BillingEventProcessor>>();            
loggerMock.Setup(l => l.Log(
It.IsAny<LogLevel>(), 
It.IsAny<EventId>(), 
It.IsAny<IReadOnlyList<KeyValuePair<string, object>>>(), 
It.IsAny<Exception>(), 
It.IsAny<Func<object, Exception, string>>()));

And a unit that verifies a call to Log() has been made:

logger.Verify(l => l.Log(
It.IsAny<LogLevel>(), 
It.IsAny<EventId>(),
It.IsAny<IReadOnlyList<KeyValuePair<string, object>>>(),
It.IsAny<Exception>(), 
It.IsAny<Func<object, Exception, string>>())
);

Prior to updating, the IReadOnlyList<KeyValuePair<string, object>> type was of type FormattedLogValues.

In v2, FormattedLogValues was a public class but in v3.1.1, FormattedLogValues is an internal readonly struct. This change appears to be related to the test failures.

I've tried using It.IsAny for that 3rd parameter instead of the IReadOnlyList or FormattedLogValues but I didn't have any luck with that.

Anyone know how I can alter this test code to make the test pass? I can see from debugging info that a call to the Log method is made as expected, I just can't figure out how to set up these mocks correctly with the internal readonly struct parameter.

This is a .NET Core 2.2 project.

Dudeman3000
  • 551
  • 8
  • 21

1 Answers1

0

FormattedLogValues in .NET Core 3.* was changed to internal which is a bit inconvenient. The common solution using Moq is to use It.IsAnyType:

loggerMock.Verify(l => l.Log(
    It.IsAny<LogLevel>(),
    It.IsAny<EventId>(),
    It.IsAny<It.IsAnyType>(),
    It.IsAny<Exception>(),
    (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
  Times.Once);

I usually like to get a bit more out of my verify, checking the message at least:

loggerMock.Verify(l => l.Log(
    It.IsAny<LogLevel>(),
    It.IsAny<EventId>(),
    It.Is<It.IsAnyType>((o, t) => ((IReadOnlyList<KeyValuePair<string, object>>)o).Last().Value.ToString().Equals(message)),
    It.IsAny<Exception>(),
    (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
  Times.Once);

As a side note you mentioned you're setting up the logger mock. I can't think of a time where I've explicitly set up a logger mock as there usually isn't any need to.

Finally if you're doing this a lot like I do, usually in headless black boxes, consider the contrib library Moq.Contrib.ExpressionBuilders.Logging which takes care of all of the above and provides a fluent builder to easily create the verify expression. Disclaimer, I am the author.

rgvlee
  • 2,773
  • 1
  • 13
  • 20