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

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

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

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

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

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
9 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