I have some code-under-test that calls on a Java logger to report its status. In the JUnit test code, I would like to verify that the correct log entry was made in this logger. Something along the following lines:
methodUnderTest(bool x){
if(x)
logger.info("x happened")
}
@Test tester(){
// perhaps setup a logger first.
methodUnderTest(true);
assertXXXXXX(loggedLevel(),Level.INFO);
}
I suppose that this could be done with a specially adapted logger (or handler, or formatter), but I would prefer to re-use a solution that already exists. (And, to be honest, it is not clear to me how to get at the logRecord from a logger, but suppose that that's possible.)
For log4j2 the solution is slightly different because AppenderSkeleton is not available anymore. Additionally, using Mockito, or similar library to create an Appender with an ArgumentCaptor will not work if you're expecting multiple logging messages because the MutableLogEvent is reused over multiple log messages. The best solution I found for log4j2 is:
I've needed this several times as well. I've put together a small sample below, which you'd want to adjust to your needs. Basically, you create your own
Appender
and add it to the logger you want. If you'd want to collect everything, the root logger is a good place to start, but you can use a more specific if you'd like. Don't forget to remove the Appender when you're done, otherwise you might create a memory leak. Below I've done it within the test, butsetUp
or@Before
andtearDown
or@After
might be better places, depending on your needs.Also, the implementation below collects everything in a
List
in memory. If you're logging a lot you might consider adding a filter to drop boring entries, or to write the log to a temporary file on disk (Hint:LoggingEvent
isSerializable
, so you should be able to just serialize the event objects, if your log message is.)Another option is to mock Appender and verify if message was logged to this appender. Example for Log4j 1.2.x and mockito:
Here is what i did for logback.
I created a TestAppender class:
Then in the parent of my testng unit test class I created a method:
I have a logback-test.xml file defined in src/test/resources and I added a test appender:
and added this appender to the root appender:
Now in my test classes that extend from my parent test class I can get the appender and get the last message logged and verify the message, the level, the throwable.
Using Jmockit (1.21) I was able to write this simple test. The test makes sure a specific ERROR message is called just once.
There are two things that you might be trying to test.
Those two things are actually different things, and so could be tested separately. However, testing the second (the text of messages) is so problematic, I recommend against doing it at all. A test of a message text will ultimately consist of checking that one text string (the expected message text) is the same as, or can be trivially derived from, the text string used in your logging code.
Note that having your program code (implementing some business logic, perhaps) directly calling the text logging interface is poor design (but unfortunately very commom). Code that is responsible for business logic is also deciding some logging policy and the text of log messages. It mixes business logic with user interface code (yes, log messages are part of your program's user interface). Those things should be separate.
I therefore recommend that business logic does not directly generate the text of log messages. Instead have it delegate to a logging object.
implements
aninterface
, which describes the internal API your business logic may use.interface
.You can then test that your business logic classes correctly tell the logging interface about events, by creating a mock logger, which implements the internal logging API, and using dependency injection in the set up phase of your test.
Like this: