Nerd For Tech
Published in

Nerd For Tech

Log4J2 and JUnit 5 | How to test logs with JUnit 5

Photo by Petri R on Unsplash

Recently I had the problem, that I needed to log some warnings for my application. Since I normally work test driven, it was pretty important to me, that I could create some unit tests for it. My test engine is JUnit 5. I found the logging library Log4J, which has a new name since version 2 (Log4J2) and used this one.

What is Log4J2?

Apache Log4j 2 is an upgrade to Log4j, which is pretty common in the industry, that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback’s architecture.

How to use Log4J2

Before we can use Log4J2 we have to configure some stuff.

Dependencies

First of all, let’s take a look at the dependencies:

As you see, I have added two implementations for log4j, one is the api and the second one is the core itself. They should be the same version. Also, I have added the logCaptor from hakky54. This will be pretty important later for the unit tests.

Configuration files

Log4J needs configuration files, so that it can show only the relevant logs and not all of them.

  1. Log4j will inspect the “log4j.configurationFile” system property and, if set, will attempt to load the configuration using the ConfigurationFactory that matches the file extension.
  2. If no system property is set the YAML ConfigurationFactory will look for log4j2-test.yaml or log4j2-test.yml in the classpath.
  3. If no such file is found the JSON ConfigurationFactory will look for log4j2-test.json or log4j2-test.jsn in the classpath.
  4. If no such file is found the XML ConfigurationFactory will look for log4j2-test.xml in the classpath.
  5. If a test file cannot be located the YAML ConfigurationFactory will look for log4j2.yaml or log4j2.yml on the classpath.
  6. If a YAML file cannot be located the JSON ConfigurationFactory will look for log4j2.json or log4j2.jsn on the classpath.
  7. If a JSON file cannot be located the XML ConfigurationFactory will try to locate log4j2.xml on the classpath.
  8. If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.

This is an example of a basic xml- configuration file. It uses the console to print out the logs in the defined pattern. It will look something like this:

07:39:56.263 [main] WARN  ClassToLog - Error Message

Probably the most important part is the Logger tag. We can define the name of it, which is mostly the name of the class we need to log.

Next we have the level. This is the log level where we can define, what exactly should get logged. Possible values are for example: error, warn, info and so on.

The additivity is also important. If we would leave that part out, the message would get printed out two times. The first time from the root logger and a second time from the custom logger. We have to set additivity to false to prevent that.

For further informations about Log Levels, I have created another blogpost:

How to log

This my Class to log. It creates a new logger with the help of the LogManager. getLogger() can also have a string parameter, but since we would use the classname anyway, which is the default, we don’t have to set it.

Then we can call logger.warn(“”) with the error message in it. It doesn’t have to be necessary a String. It could also be an exception or a number.

We could also set some other values instead of warn. Valid values for this attribute are “trace”, “debug”, “info”, “warn”, “error” and “fatal”.

How to test the logs

Since we like to test everything with a unit test, let’s do that also with the logs. Sadly there is no appender integrated in JUnit to test the logs. We would need to define it by ourselves and therefore produce a lot of boilerplate code.

The better solution is to use the plugin, that i added before in the build.gradle.kts. It manages all the stuff for us and is really easy to use.

  1. Create object of the Class we want to test, so we can perform a function call on it later.
  2. Create the logCaptor. The Classname needs to be the same as defined before.
  3. Now we can call the function that should log the warning.
  4. With logCaptor.warnLogs, we can exactly see which logs of the level “warning” got logged for this class logger. Since we only logged one warning in this class, we can use containsExactly().

Reflection

What went good?

The logging itself with Log4J2 worked pretty good and fast. I was able to log some really basic stuff pretty easily. Also the logCaptor worked better as expected.

What needs improvement?

The biggest problem I had was, that there are so many resources on how to test the logs with JUnit, but actually they all use either an old version of Log4J, or use JUnit4 which is pretty different than JUnit5. For example the Rule annotation is deprecated in JUnit 5. I tried so many things with coding the appender by myself, but it kept failing. In the end the logCaptor was the only real solution for me.

I hope this article could help you. Happy logging 😊

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store