Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verifying method call with any struct parameter in Moq

I'm running into the following problem when migrating my app from ASP.NET Core 2.2 to ASP.NET Core 3.0:

I have a class which should log an error message under certain circumstances. This is done by calling LogError on ILogger<MyClass>.

I used to verifiy this with the following snippet from my unit test:

Mock<ILogger<MyClass>> loggerMock = ...;
MyClass myClass = ...;

myClass.MethodThatLogsTestException();

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

Now here lies the problem:

In ASP.NET Core 2.2, the 3rd parameter (Mocked via It.IsAny<object>()) was of an internal type FormattedLogValues. This was a class, so It.IsAny<object>() worked. In ASP.NET Core 3.0 it was changed to a struct, so It.IsAny<object>() no longer matches it.

How can I get my Verify() call to work in ASP.NET Core 3.0? Is there an It.IsAny() version that matches any struct type?

Edit: Here is a fully runnable snippet that fails on ASP.NET Core 3.0 and succeeds on ASP.NET Core 2.2.

public class Test
{
    public class Impl
    {
        private readonly ILogger<Impl> logger;

        public Impl(ILogger<Impl> logger)
        {
            this.logger = logger;
        }

        public void Method()
        {
            logger.LogError(new Exception(), "An error occurred.");
        }
    }

    [Fact]
    public void LogsErrorOnException()
    {
        var loggerMock = new Mock<ILogger<Impl>>();
        var sut = new Impl(loggerMock.Object);

        sut.Method();

        loggerMock.Verify(l => l.Log(
            It.IsAny<LogLevel>(),
            It.IsAny<EventId>(),
            It.IsAny<object>(),
            It.IsAny<Exception>(),
            It.IsAny<Func<object, Exception, string>>())
        );
    }
}
like image 924
Nicky Muller Avatar asked Sep 18 '19 09:09

Nicky Muller


Video Answer


2 Answers

Changing It.IsAny<Func<object, Exception, string>>()) to (Func<object, Exception, string>) It.IsAny<object>() seems to solve the problem. object can also be replace by IsAnyType if you're on Moq 4.13+.

Internally the Logger class uses FormattedLogValues as the state parameter (the object in my example). The change to struct seems to have something to do with it. What exactly the cause is I'm not sure. But there seems to be an issue on the Moq GitHub repo describing a few more details. There doesn't seem to be a concrete explanation yet why it used to work, but more info will probably be posted there soon.

https://github.com/moq/moq4/issues/918

like image 154
Nicky Muller Avatar answered Oct 13 '22 09:10

Nicky Muller


I found the same problem in Github.

I made an extension method for the solution:

public static void VerifyLog<T>(this Mock<ILogger<T>> mockLogger, Func<Times> times)
{
    mockLogger.Verify(x => x.Log(
        It.IsAny<LogLevel>(),
        It.IsAny<EventId>(),
        It.Is<It.IsAnyType>((v, t) => true),
        It.IsAny<Exception>(),
        It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)), times);
}
like image 45
Tolga Kayhan Avatar answered Oct 13 '22 09:10

Tolga Kayhan