I am trying to write some unit tests for controller actions. To do that, I am using XUnit and Moq. The controllers have an ILoggerFactory injected in the constructor. How does one Moq this up for testing?
I have tried mocking a Logger for the controller class, and then mocking up CreateLogger to return the mock Logger, but I keep getting various test runtime NullReferenceExceptions when the LogInformation() function is called.
// Logger that yields only disappointment... var mockLogger = new Mock<ILogger<JwtController>>(); mockLogger.Setup(ml => ml.Log(It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>())); var mockLoggerFactory = new Mock<ILoggerFactory>(); mockLoggerFactory.Setup(mlf => mlf.CreateLogger("JwtController")).Returns(mockLogger.Object);
I assume the problem is that LogInformation is being called, and this is an extension method, so how to moq that?
To mock an ILogger<T> object, we can use Moq library to instantiate a new Mock<ILogger<T>>() object. Make sure you have installed the latest Moq package (v4. 15.1 as of Nov. 16, 2020, which contains an update to support “nested” type matchers).
You can use Moq to create mock objects that simulate or mimic a real object. Moq can be used to mock both classes and interfaces.
Mock objects allow you to mimic the behavior of classes and interfaces, letting the code in the test interact with them as if they were real. This isolates the code you're testing, ensuring that it works on its own and that no other code will make the tests fail.
Moq is a mocking framework for C#/. NET. It is used in unit testing to isolate your class under test from its dependencies and ensure that the proper methods on the dependent objects are being called. For more information on mocking you may want to look at the Wikipedia article on Mock Objects.
For what it's worth: instead of mocking an ILoggerFactory
, you could also pass an instance of NullLoggerFactory
. This NullLoggerFactory
will return instances of NullLogger
. According to the docs, this is a:
Minimalistic logger that does nothing.
I just mock the ILogger extension methods as below, and use a value function in the ILoggerFactory setup that returns the Mock ILogger object.
var mockLogger = new Mock<ILogger<[YOUR_CLASS_TYPE]>>(); mockLogger.Setup( m => m.Log( LogLevel.Information, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>())); var mockLoggerFactory = new Mock<ILoggerFactory>(); mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(() => mockLogger.Object);
This will return your mocked Logger and you can verify any calls on it. No need to write wrappers or helpers.
You can even mock the IsEnabled, which is necessary for some code that leverages that functionality.
mockLogger.Setup( m => m.IsEnabled( Microsoft.Extensions.Logging.LogLevel.Debug)).Returns(true);
Because there is only one method to mock, (and that you have to call), below shows the logging call you (might) use to have everything pointed to the this exact method.
catch (ArgumentOutOfRangeException argEx) { // this.logger.LogError(argEx, argEx.Message); /* << this is what you would like to do, BUT it is an extension method, NOT (easily) mockable */ Func<object, Exception, string> returnAnEmptyStringFunc = (a, b) => string.Empty; this.logger.Log(LogLevel.Error, ushort.MaxValue, argEx.Message, argEx, returnAnEmptyStringFunc); throw argEx; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With