Friday, 20 March 2009

Quick step-by-step guide - Upgrading JUnit 3.x to JUnit 4.x

Well we finally got around to upgrade all our unit tests to new features. About time really!

We use maven and the maven-surefire-plugin for running our unit tests during our build process. Let’s look at the steps I did to get migrate.

  • All our test cases extend from a single abstract test case class called AbstractLiveTestCase that in our Junit 3 integration extended from junit.framework.TestCase. For Junit 4, tests are described with annotations so we don’t need to extend this class anymore – so I (1) removed the “extends TestCase” from our AbstractLiveTestCase
  • The Junit 3 TestClass class used to provide all the handy assert functions. These are now provided as org.junit.Assert static methods so I went through each of our tests and *(2) added “import static org.junit.Assert.;”** to the top of each class. With the handing auto source code formatting in Eclipse enabled, Eclipse automatically expanded the “org.junit.Assert.*” imports to the explicit ones needed for that class when I saved it.
  • And finally (3) Added @Test to each test method so that Junit would know what tests to run. Basically that was it – surefire/maven/junit picked up the test cases in the new style – and in most cases that’s job done. However we wanted to go a little further.

We had also implemented in Junit 3 a handy feature that outputted a quick ERROR message in the system out of a test run so we could quickly see the failure. By default maven’s surefire simply tells you there was a failure, but you have to look at a file to see what it was. Bit of pain that, so in our Junit 3 extension we would provide more info on the screen when a failure occurred, for example we might see something like:

Running ERROR : ** Test Failure ** Site home not correct expected: but was: @ line 17 of ( Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.01 sec «< FAILURE!

In Junit 3 we did this by overriding the runTest method of the TestCase class. In Junit 4 we can use runners and listeners. To start with we set the @RunWith annotation on our AbstractLiveTestCase to indicate that all of our tests should run with a specified runner …

public abstract class AbstractLiveTestCase {

The runner was defined to register a listener …

public class BemokoTestRunner extends BlockJUnit4ClassRunner {
	public BemokoTestRunner(Class<?> clazz) throws InitializationError {
	public void run(final RunNotifier notifier) {
		notifier.addListener(new BemokoTestListener());;

and the listener was defined to listen for failures …

public class BemokoTestListener extends RunListener {
	private static Log log = LogFactory.getLog(BemokoTestListener.class);
	public void testFailure(Failure failure) {
		Throwable t = failure.getException();
		if (t instanceof Exception) {
			log.error("*** Test Failure *** " + t.getMessage(), t);
		} else {
			if (t.getMessage() != null) {
				log.error("*** Test Failure *** " + t.getMessage() + " @ "
						+ getErrorLocation(t.getStackTrace()));
			} else {
				log.error("*** Test Failure *** ", t);
	public void testIgnored(Description description) {
		if (log.isWarnEnabled()) {
			log.warn("+++ Test ignored +++ " + description.getDisplayName());
	private String getErrorLocation(StackTraceElement[] st) {
		for (StackTraceElement ste : st) {
			if (ste.getClassName().startsWith("com.bemoko")
			    && !ste.getClassName().contains("AbstractLiveTestCase")) {
			  return "line " + ste.getLineNumber() + " of " + ste.getClassName();
		return "(line number and class not known)";

in this way I can handle events from my tests in any way I see fit and much more elegant that the Junit 3 implementation I previously had.

Job done and we’re all sorted for our unit test framework for the foreseeable future.