It's usual to see logging functionality in the code:
public class A { private static final Log LOG = LogFactory.getLog(A.class);
and usage:
} catch (Exception e) { LOG.error(e.getMessage(), e); throw e; }
but I never saw even single unit test for such code.
Off course I do test throwing exception and exception type, but should I write test for checking logging information? I tend to think that logging is another part of system behavior, so it's quit logically to cover it in the tests.
Assuming that I should cover it, means that I should change my original code to inject mock log and check that "error" method was invoked with expected message. But what to do if my original class is service and it's instantiated by spring, should I inject some logger as well as other dependencies?
Logs serve for investigating an error after the fact. But when you do unit testing, you have all the power to automate the search for errors. If you want to look into logs, it means, you don't have enough test cases and checks.
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.
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