Back to all posts

Unit Testing Extension Methods Such as LogWarning

Posted on Jun 21, 2021

Posted in category:
Development
C#

Unit testing is still a major struggle point for many developers, and it is understandable why it is so hard to comprehend when we look at typical implementations and examples. Unit testing when Extension Methods are used is a bit of a nightmare; statics provide a similar difficulty that often causes development teams to "skip" unit testing in favor of efficiency/time.

We are in a constant struggle as developers trying to balance being able to deliver code quickly and developing code that is easy to test. In a recent project, I found a need to validate several logging calls, but I was using the simple LogWarning(string) call. Thankfully I found a great way to assert these items with minimal effort.

The Example

I tried to test that I logged an exception with a specific text in a conditional path. The following snippet shows my method, with actual logic trimmed for brevity.

Sample Class To Test
public EmailFactory : IEmailFactory
{
    private readonly ILogger _logger;

    public EmailFactory(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.Create&;t;EmailFactory>();
    }

    public void MyMethod()
    {
        _logger.LogWarning(new Exception(), "Unable to Work")
    }
}
    

This is a very common practice, but we cannot test as LogWarning is an extension method.

The Solution

Unit test mocks can only be used to validate the final, actual calls. An extension method is nothing more than a helper for developers to write less code. When our code executes, the underlying code is still called. Due to this, I can use an assert similar to the following to validate the above call. (This assumes your mock is named _loggerMock.

Working Assertion
_loggerMock.Verify(
    x => x.Log(
        LogLevel.Warning,
        It.IsAny<EventId>(),
        It.Is<It.IsAnyType>((o, t) => string.Equals("Unable to Work", o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
        It.IsAny<Exception>(),
        (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
    Times.Once);
    

Long-term, we could create helpers for our unit tests or otherwise to repeat this process, but by looking at the source for the LogWarning() methods we can determine what the real call should look like, and we can then validate the actual call.

You can see a full example of this in the ICG .NET Core SMTP Utility Library.

I hope this helps you improve your unit testing!