Unit Testing Logging
As part of the upgrade we’re planning on making to CruiseControl.NET we are using NLog for logging messages. We are also aiming to increase the unit test coverage of the code. So the question arises – how can we test that logging is actually working as it should be?
There’s actually two parts to this question:
- Are the logs being generated correctly?
- Are we putting the right stuff in the logs?
Question #1 is really outside our scope – considering the number of different ways the logging can be configured we’d have to duplicate all the tests in NLog itself. So rather than doing this we’re just going to assume that the tests in NLog will pick up any errors with log generation.
That leaves the second question – how can we know we are putting the right stuff into the logs? The short answer is we can’t – but then there is no way that we can guarantee that any code we write is 100% correct!
Instead what I’m interested in doing is checking that we are logging some expected stuff. In other words how can we intercept the logging to ensure that we are logging the expected messages?
To answer this question we need a little bit of background. Logging in NLog can be configured either via a configuration file or via code. Now the recommended way is the config file so it can be changed without forcing a re-compile. But for us the way that we will use is via code.
NLog also has the concept of Targets: where the output is written to. There is a wide variety of different targets for different scenarios (console, file, e-mail, database, etc.) Of interest in unit testing is the Memory target. This will write the log messages to an array in memory.
So all we need is some way to wire up the logging to a memory target and then check the messages that are being written. It would also be nice if the unit tests reset the logging after they’ve finished just in case there has been some configuration in the unit test config file.
To make this easy I have added a helper class to our unit tests that simplifies this whole process. This is a disposable class with a static method for initialising the whole process. To use this class looks something like:
using (var intercept = LogHelper.InterceptLogging(typeof(TypeBeingTested)))
{
// Tests go here
var expected = new[]
{
// Expected messages go here
"LogLevel|TypeBeingTested|Message"
};
CollectionAssert.AreEqual(expected, intercept.Messages);
}
Since the class implements IDisposable it can be used in a using statement. This will ensure that the configuration is reset after the tests have finished.
The static method takes in an array of types. These types are the loggers that should be intercepted – after all there can be other messages occurring that we don’t want to intercept.
To actually check the results we need to compare the Messages property on the class with the expected messages. This is just a facade property that exposes the messages from the underlying memory target. As in the example above the messages consist of three parts: loglevel (e.g. debug, info, error, etc.), the loggger (this will be one of the types in the array) and the actual message.
Nice and simple, without adding too much overhead to the testing process.
Let me know if anyone is interested in the actual code behind LogHelper and I will post it. Otherwise it is in the Subversion repository for CruiseControl.NET.
Hi Craig, great blog!
I am in the midst of writing unit tests to verify that log messages are written to the log under certain conditions. Could you please post your source code for the LogHelper class as it may come in very handy indeed
Cheers, Jones
Hi Jones,
All the code is in Subversion on SourceForge. You can view the source online at .
Craig
Hi Craig, I found it and I’m working on my first test with it, thanks again.
Cheers, Jones
Hi again, Craig, are you using NLog 2.0 ?
Yes – NLog 2.0. Not sure what would be needed to convert to a different version – I can’t remember what I did for the previous version of NLog
Craig
Thank you for the quick reply. I got it working by changing type of the Messages property from IList to ArrayList.
Cheers, Jones
That’s indeed a very neat way of testing logging.
Do we really need it? I mean testing the logging itself.
What would the benefit be of the following scenario :
° existing code logs ‘Project X starts integration’
° I update the logging to ‘Project X starts integration at HH:MM’
and now some tests fail, because I updated the log message. What is the added value?
Do not get me wrong, I really like testing, but I wonder if testing log messages is not a bit over the top?
It is not that these log messages will be used as input or so for another program or whatever. If it would be that case, than I can understand that we test it. But than it would not be ‘log’ messages anymore, but real data.
For me the main point is to test that we are logging messages. There are parts of the code that do nothing other than log messages (e.g. some warnings, the null task, etc.) There are also some cases where we are interested in what messages are produced, for example checking the flow of logic between different components.
The other use that testing logging is as a safety net. In some places we have specific messages (e.g. errors) and we want to ensure that they are not lost.
So I don’t think we should go overboard and test every log message that is being generated. Instead we should just be targeting the cases where it will provide value.
Craig
Good point : testing the error messages is indeed a plus.
It’s like creating a unit test where you expect a certain exception or exception message, and we want to be sure that it gets logged also.
That’s indeed added value