Friday, 10 September 2010

Fun with JUnit4

I decided it was well past time to take a closer look at JUnit4. If you are still using JUnit3 you need to know that JUnit4 uses annotations and, when used with Spring, you can use the annotations to load an application context. Here is a sample test:

public class RunDatabaseScriptsTest{
    @Autowired private RunDatabaseScripts m_runDatabaseScripts;

    public void testExecute() throws Exception
What this does is run the method called testExecute which, unlike JUnit 3, can be named anything you like. With the @RunWith and @ContextConfiguration in place Spring will load the file RunDatabaseScriptsTest-context.xml and it will inject the bean called runDatabaseScripts into the autowired field. The autowired field does not even need a getter and setter.

So this is nice and succinct and you don't need to extend a class from Spring like you had to in JUnit3. It gave me some ideas though.

We use JNDI names to define our database settings, normal enough in things deployed to an application server. We also use them in our tests, so our bean definitions have JNDI references. We could just use a DriverManagerDataSource bean injected with the database settings but we have 3rd party library that everything depends on and that is most easily configured via JNDI.

The question arises, though, how to ensure our JNDI names have been set up before Spring loads the beans. Any later and Spring will complain about the missing JNDI. So I took a look at that @RunWith annotation which names the class Spring uses to do its work.

But before we go there I need to mention that Spring has added an easy way to extend its runner. You just add an annotation:


near the @RunWith and you can supply a list of listener classes. These implement 


which has methods to run before and after each test as well as before the first test and after the last test. This is useful, but not in my case because they all run after the beans are loaded which is too late for me. Also you cannot configure any options on these listeners which is limiting.

Now, back to the @RunWith which specifies the SpringJUnit4ClassRunner class. I extended this class so I could override this method:

protected TestContextManager createTestContextManager(Class clazz) {
        return new TestContextManagerLocal(clazz);

This is so that I can get the class to take my own TestContextmanager. The TestContextManager is the key to this. All it has to do is extend Spring's TestContextmanager and override the registerExecutionListeners method.

Spring gathers its internal TestExecutionListener and the ones defined in the @TestExecutionListener and passed them all to this for registration. To get what I want I need to slip the JNDI loader into this list at the first position.

    public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) {
        JNDILoad jndi = getTestContext().getTestClass().getAnnotation(JNDILoad.class);
        if (jndi != null)
            super.registerTestExecutionListeners(new JNDIResources(jndi));
        for (TestExecutionListener listener : testExecutionListeners) {
        for (Annotation annotation : getTestContext().getTestClass().getAnnotations())
            TestExecutionListener testExecutionListener = getTestExecutionListener(annotation);
            if (testExecutionListener != null)

First we check the test class for an annotation @JNDI and if it is there we register it. Then we register the list we were passed. Finally I loop through other annotations on the test class. There's a method that figures out if we can use this annotation as a listener (it's a simple lookup/translate to a class thing, you can work out your own easily enough) and we register that too.

So now my test class looks like this:

public class SampleTestCase {

The @RunWith now names my own runner class. @ContextConfiguration is still there and still works the same way. The new annotations are @JNDILoad which specifies my JNDI loader. There's a simple implementation that reads data sources from a file and load them into a mock JNDI implementation. The main point is that it does this first.

The @DBLoad loads a listener that is configurable (as opposed to the ones defined in @TestExecutionListener which aren't). This particular one runs a database script using the connection defined in the bean. I use it when I am testing with a hypersonic or other in-memory database to pre-load the data.

Post a Comment