Should we unit test logging?
If the logging is a business requirement, and will provide business value (i.e. in the event of a failure, to diagnose or triage a problem), then you should treat it as any other requirement. As such, you should probably write unit tests not to verify that your logging library works, but to verify that, under the expected circumstances, your code logs what it should.
More on this topic: https://ardalis.com/logging-and-monitoring-are-requirements
It's not up to you to test the logging library. But it can be worthwhile to test that when an exception is thrown, your class logs a message at the right level. What you're testing is that your code does the right thing with the logging library.
To make the code above testable, use dependency injection. This assumes that the logger implements an interface, ILog
. You would pass in the logger as a constructor parameter to class A. Then the test code would create a mock implementation of ILog
, and pass that into the constructor. Not shown in the code above is how the exception comes about, but presumably it would be through some other dependent object. So you mock that as well, and make it throw an exception. Then check that the mock ILog
invoked the error
method. Maybe you want to examine the message that it logs, but that might be going too far, by making the test code fragile.
Yes, we should test logging when the logging is doing something that is required. For example, you have hooks in some external application that scans the log for certain events. In that case you certainly want to ensure the logging is done.
Of course you do not want to test every loging event, and I would think that mostly only ERROR (and not all of them) should be tested.
With modern logging frameworks such as SLF4j you can simply inject a custom handler that stores the events for in memory and that can be asserted against afterwards.
There are two of them that come to my mind right now:
SLF4JTesting: Requires no modification of logging configuration but requires to inject a logging factory which might lead to modified code.
SLF4J Test: Not as powerful as slf4jtesting and seems not to be developed, but works well with existing code. No modifications besides the logger configuration for test.
When using SLF4J Test, the assertions are quite strict and check the whole event for equality. A custom matcher is probably interesting in such a case:
public static Matcher<LoggingEvent> errorMessageContains(final String s) {
return new TypeSafeMatcher<LoggingEvent>() {
@Override
public void describeTo(final Description description) {
description.appendText(" type " + Level.ERROR + " should contain ")
.appendValue(s);
}
@Override
protected void describeMismatchSafely(final LoggingEvent item, final Description mismatchDescription) {
mismatchDescription.appendText(" was type ").appendValue(l)
.appendText(" message ").appendValue(item.getMessage());
}
@Override
protected boolean matchesSafely(final LoggingEvent item) {
return item.getLevel().equals(Level.ERROR)
&& item.getMessage().contains(s);
}
};
}
This only checks that the message contains a text but not if it is equal. Thus, when the message is modified to fix a typo or give more detail, the test does not break if the essential part is still contained.