Unit Test and Mock Logger Methods in .NET Core
In this article, we shall see a few best approaches to theUnit Test and Mock Logger Methods in the C# .NET Core application.
As we know .NET Core framework has brought in and leveraged the Dependency Injection (DI) principle very well and almost each and every functionality/service used in the application can be injected through DI.
The ILogger interface works very nicely with the .NET Core ecosystem and today in this post we will learn how to Unit Test logging in a .NET Core-based application.
Today in this article, we will cover below aspects,
What and How to Unit Test and Mock Logger Methods ??
Logging is injected using a Dependency Injection principle using Interface ILogger. ILogger being an interface, it is very simple to mock.
Please remember logger interface exposes multiple Extension methods few listed below,
- LogInformation
- LogError
- LogDebug
- LogWarning
- LogCritical
Here is a sample code that uses ILogger methods for logging purposes.
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
var result = false;
try
{
_logger.LogInformation("Application {applicationEvent} at {dateTime}", "Started", DateTime.UtcNow);
result = _business.PerformBusiness();
_logger.LogInformation("Application {applicationEvent} at {dateTime}", "Ended", DateTime.UtcNow);
}
catch (Exception ex)
{
//Return Business exception repsponse here
//Log technical exception
_logger.LogError(ex.Message);
return new StatusCodeResult(500);
}
return Ok(result.ToString());
}
For the above code, we shall be writing Unit testing using the code-first approach.
Do not Unit test and Mock everything – Code ‘which we don’t own
As a first principle, you unit test code that you own and Mock everything else which you don’t own.
The second important thing is Unit tests should not log to a file or console or any other location.
This can be achieved by mocking the ILogger interface as shown below,
Mock ILogger Interface
var _mockLogger = new Mock>();
We shall only make sure the logger method is invoked.
[Fact]
public void BookService_Get_ValidBookID_Success()
{
//Arrange
var _mockBusiness = new Mock<IBusinessLayer>();
var _mockLogger = new Mock<ILogger<ValuesController>>();
int input = 12;
int expectedloggerInvocationCount = 2;
string expectedResult = "True";
_mockBusiness.Setup(x => x.PerformBusiness()).Returns(true);
//Act
ValuesController controller = new ValuesController(_mockLogger.Object, _mockBusiness.Object);
var result = controller.Get(input).Result as ObjectResult;
//Assert
Assert.Equal(HttpStatusCode.OK, (HttpStatusCode)result.StatusCode);
Assert.Equal(expectedResult, result.Value);
Assert.Equal(expectedloggerInvocationCount, _mockLogger.Invocations.Count);
Assert.Equal(LogLevel.Information, _mockLogger.Invocations[0].Arguments[0]);
Assert.Equal(LogLevel.Information, _mockLogger.Invocations[1].Arguments[0]);
}
As above I am doing 2 types of validation related to the logger unit test.
1. Logger Invocation count
Count the actual number of logger invocations for a given scenario. Remember this invocation will vary for each scenario.
In the above example, I have set “int expectedloggerInvocationCount = 2” and validated this count using the below Assert(),
Assert.Equal(expectedloggerInvocationCount, _mockLogger.Invocations.Count);
Alternatively,
You can use XUnit mock methods to verify if logger methods are called Once or Twice, etc.
One can use the below code to achieve the same,
As I verified, the below code works for ASP.NET Core 3.1 or .NET 5 also,
_mockLogger.Verify(x => x.Log(LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
Times.Exactly(2));
The above code is validating that the LogInformation is getting called 2 times in a given method.
This is useful when you are making sure developers don’t miss to call logging at least 2 times i.e start and at end of the method call. Very useful indeed for debugging and troubleshooting purposes in lower environments.
The above code matches with the below-overloaded Log method template,
Isn’t the above two techniques sufficient to validate the logger in the form of unit test cases?
I found both techniques to be very handy. A logger is an integral part of our codebase which gets invoked in almost every method.
With these Assert,
- I am making sure the overall logger invocation happens in a given method.
- If someone removes a logging statement from a method, the Unit Test fails. Easy to find any breaking changes, indeed.
2. Verify LogType Information Vs Error etc.
The above validation on the invocation count works fine and was very easy. One may want additional validation like checking if Information or Error logs were used properly or not including their sequence if any. ( You will many such examples or use cases where exception handling is the most important aspect)
Such validation is very easy to implement as shown below.
LogType will always hold in Arguments[0] hence can be easily asserted.
Invocation[n] will vary depending on the number of times the logger is invoked in a method for a given scenario.
Similarly, Verify the Exception Logs using LogLevel.Error
Or
If Unit testing an Exception scenario and want to make sure the developer logs exception without fail, then you can add the above validation in your unit test code to make your test fails and then refactor your actual method for exception logging code.
A logger is an existing component and we don’t own it. I believe we should not get crazy around logger unit testing.
I have seen many writing extra code/components to the Unit Test Logger which I believe is an overhead.
With above mentioned two approaches, we get basic mocking and needed asserts and we can restrain reinventing a wheel.
References:
That’s All! Happy Coding.
Do you use any other better approaches for testing logging? Please sound off your comments below.
Conclusion
Today we learned a simple approach for unit testing logger interface. Sometimes framework dependent extension methods are surprisingly difficult to test. In such scenarios, the verifying method invocation like once or twice, etc. rather than actual results helps. With the above-mentioned invocation count and log, type assert approaches, we achieve logger unit testing properly.
Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.
Hey,
Im using Mock to test my test cases. However I need to test following peice of code:
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug($”Received body: {body}”);
}
But when I call the Logger mock, isEnabled function return false for all the loglevel. How can I overcome it
Thanks for your KJ.
If you don’t specify the IsEnabled flag then by default it will be false.
Please add the below code for the mock flag to be set. IsEnabled is a regular method hence easy to mock( like other extension methods as difficult to mock)
_mockLogger.Setup(x => x.IsEnabled(LogLevel.Debug)).Returns(true);
That should set the required mock.
Hope it helps.
Thanks. Worked for me ..In fact I had written my own wrapper for mocking logging purpose which I have removed now …
Perfect. Glad it helped you !