In my previous post I showed some tips on unit testing JavaBeans. In this blog entry I will give two more tips on unit testing some fairly common Java code, namely utility classes and Log4J logging statements.
Testing Utility classes
If your utility classes follow the same basic design as the ones I tend to write, they consist of a final class with a private constructor and all static methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package it.jdev.example; import static org.junit.Assert.*; import java.lang.reflect.*; import org.junit.Test; /** * Tests that a utility class is final, contains one private constructor, and * all methods are static. */ public final class UtilityClassTester { private UtilityClassTester() { super(); } /** * Verifies that a utility class is well defined. * * @param clazz * @throws Exception */ @Test public static void test(final Class<?> clazz) throws Exception { // Utility classes must be final. assertTrue("Class must be final.", Modifier.isFinal(clazz.getModifiers())); // Only one constructor is allowed and it has to be private. assertTrue("Only one constructor is allowed.", clazz.getDeclaredConstructors().length == 1); final Constructor<?> constructor = clazz.getDeclaredConstructor(); assertFalse("Constructor must be private.", constructor.isAccessible()); assertTrue("Constructor must be private.", Modifier.isPrivate(constructor.getModifiers())); // All methods must be static. for (final Method method : clazz.getMethods()) { if (!Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(clazz)) { fail("Non-static method found: " + method + "."); } } } } |
This UtilityClassTester itself also follows the utility class constraints noted above, so what better way to demonstrate its use by using it to test itself:
1 2 3 4 5 6 7 8 9 10 11 12 |
package it.jdev.example; import org.junit.Test; public class UtilityClassTesterTest { @Test public void test() throws Exception { UtilityClassTester.test(UtilityClassTester.class); } } |
Testing Log4J logging events
When calling a method that declares an exception you’ll either re-declare that same exception, or you’ll try to deal with it within a try-catch block. In the latter case, the very least you will do is log the caught exception. A very simplistic example is the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package it.jdev.example; import java.lang.invoke.MethodHandles; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class MyService { private static final Logger LOGGER = Logger.getLogger(MethodHandles.Lookup.class); @Autowired private MyRepository myRepository; public void doSomethingUseful() { try { myRepository.doSomethingVeryUseful(); } catch (SomeException e) { LOGGER.error("Some very informative error logging.", e); } } } |
Of course, you will want to test that the exception is logged appropriately. Something along the line of the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package it.jdev.example; import static org.junit.Assert.*; import org.apache.log4j.spi.LoggingEvent; import org.junit.*; import org.mockito.*; public class MyServiceTest { @Mock private MyRepository myRepository; @InjectMocks private MyService myService = new MyService(); @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void thatSomeExceptionIsLogged() throws Exception { TestAppender testAppender = new TestAppender(); Mockito.doThrow(SomeException.class).when(myRepository).doSomethingVeryUseful(); myService.doSomethingUseful(); assertTrue(testAppender.getEvents().size() == 1); final LoggingEvent loggingEvent = testAppender.getEvents().get(0); assertEquals("Some very informative error logging.", loggingEvent.getMessage().toString()); } } |
But how can you go about to achieve this? As it turns out it is very easy to add a new LogAppender to the Log4J RootLogger.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
package it.jdev.example; import java.util.*; import org.apache.log4j.*; import org.apache.log4j.spi.*; /** * Utility for testing Log4j logging events. * <p> * Usage:<br /> * <code> * TestAppender testAppender = new TestAppender();<br /> * classUnderTest.methodThatWillLog();<br /><br /> * LoggingEvent loggingEvent = testAppender.getEvents().get(0);<br /><br /> * assertEquals()...<br /><br /> * </code> */ public class TestAppender extends AppenderSkeleton { private final List<LoggingEvent> events = new ArrayList<LoggingEvent>(); public TestAppender() { this(Level.ERROR); } public TestAppender(final Level level) { super(); Logger.getRootLogger().addAppender(this); this.addFilter(new LogLevelFilter(level)); } @Override protected void append(final LoggingEvent event) { events.add(event); } @Override public void close() { } @Override public boolean requiresLayout() { return false; } public List<LoggingEvent> getEvents() { return events; } /** * Filter that decides whether to accept or deny a logging event based on * the logging level. */ protected class LogLevelFilter extends Filter { private final Level level; public LogLevelFilter(final Level level) { super(); this.level = level; } @Override public int decide(final LoggingEvent event) { if (event.getLevel().isGreaterOrEqual(level)) { return ACCEPT; } else { return DENY; } } } } |