Core Java

Some more unit test tips

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.

 
 
 
Utility class tester

01
02
03
04
05
06
07
08
09
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:

Test case for the UtilityClassTester

01
02
03
04
05
06
07
08
09
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:

MyService example

01
02
03
04
05
06
07
08
09
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:

Test case for MyService logging event

01
02
03
04
05
06
07
08
09
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.

TestAppender for Log4J

01
02
03
04
05
06
07
08
09
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;
            }
        }
 
    }
 
}
Reference: Some more unit test tips from our JCG partner Wim van Haaren at the JDev blog.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Piotr Dudkiewicz
Piotr Dudkiewicz
10 years ago

My tips:
Testing Utility classes – use static code analysis tool like PMD
Testing Log4J logging events – use Log4jMockPolicy from Powermock

Back to top button