Extending Spring JUnit Runner

For our integration tests we had to use the development database. On that database there are some batch parameters stored and one of those parameters is a cutoffTime. As the name suggest, our business logic can not be processed after that time, so we had to create a mechanism to make sure that the tests are not executed, else the build would fail.
The first thing we did was a quick fix. We added an if-statement to the test to see what time it is and if the test can be executed. As some of you already know, an empty method annotated with @Test will execute and be successful. This is not correct, when someone breaks code that is in those tests, the test should not be marked as successful. We should be able to add the @Ignore annotation on the tests when the cutOffTime is passed.

I looked into this problem and the first thing that came to my mind was to use Annotations, because it is meta-data and not test functionality. JUnit4 has a very easy way to handle annotations, so I decided to go that way.

/**
 * The CutOffTime annotation..
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CutOffTime {
    String cutOffTime();
}

I want this annotation to cancel all my tests in a testcase, not just a method, that is why i put the ElementType on TYPE.

Our integration tests are running with the SpringJUnit4ClassRunner so I extended this. I have overridden the invokeTestMethod method, because this is the one executing each test. Here is the source code

/**
 * The Class IntegrationTestClassRunner. This class is a specific class runner
 * for the payments. There is a cutoffTime defined in the database that decides
 * if a payment that can be made or not. This can be applied by the
 * {@link CutOffTime} annotation
 *
 * @author Jelle Victoor
 * @version 14-apr-2010
 */
public class IntegrationTestClassRunner extends SpringJUnit4ClassRunner {

    /**
     * The Constructor.
     *
     * @param clazz
     *            the clazz
     *
     * @throws InitializationError
     *             the initialization error
     */
    public IntegrationTestClassRunner(final Class<?> clazz) throws InitializationError {
        super(clazz);
    }

    /** {@inheritDoc} */
    @Override
    protected void invokeTestMethod(final Method method, final RunNotifier notifier) {
        Annotation[] classAnnotations = classAnnotations();
        Boolean ignore = Boolean.FALSE;
        for (Annotation classAnnotation : classAnnotations) {
            if (classAnnotation instanceof CutOffTime) {
                if (shouldIgnore(((CutOffTime) classAnnotation).cutOffTime())) {
                    ignore = Boolean.TRUE;
                }
                break;
            }
        }
        if (!ignore) {
            super.invokeTestMethod(method, notifier);
        } else {
            notifier.fireTestIgnored(methodDescription(method));
        }
    }

    /**
     * Decides to ignore the test
     *
     * @param cuttOffTime
     *            the cutt off time
     *
     * @return true, if should ignore
     */
    private boolean shouldIgnore(final String cuttOffTime) {
        int hour = Integer.parseInt(StringUtils.substring(cuttOffTime, 0, 2));
        int minutes = Integer.parseInt(StringUtils.substring(cuttOffTime, 2, 4));
        return new LocalTime().isAfter(new LocalTime(hour, minutes));
    }
}

As you can see, the classAnnotation() method will give you all the annotations that are on your class *and are specified with @Retention(RetentionPolicy.RUNTIME)*. I simply check if the annotation is found on the class. When I’m done checking if the annotation is found and if the test should be ignored, i can fireTestIgnored on the notifier or execute the test. We just annotate our tests with @RunWith(IntegrationTestClassRunner.class).
This way the test should not fail or succeed, but now you can simply see that the test is ignored. The statistics are clean :-).

I know that the whole purpose of this extention is odd, but we are forced into this position. With this post I’m trying to show you that extending a test framework like JUnit isn’t hard at all and you can use it in every way you want to.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>