Custom JUnit 4 Test Runners

1. Overview

In this quick article, we’re going to focus on how to run JUnit tests using custom test runners.

Simply put, in order to specify the custom runner, we’ll need to use the @RunWith annotation.

2. Preparation

Let’s start by adding the standard JUnit dependency into our pom.xml:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
</dependency>

3. Implementing a Custom Runner

In the following example, we’ll show how to write our own custom Runner – and run it using @RunWith.

A JUnit Runner is a class that extends JUnit’s abstract Runner class and it is responsible for running JUnit tests, typically using reflection.

Here, we’re implementing abstract methods of Runner class:

public class TestRunner extends Runner {

    private Class testClass;
    public TestRunner(Class testClass) {
        super();
        this.testClass = testClass;
    }

    @Override
    public Description getDescription() {
        return Description
          .createTestDescription(testClass, "My runner description");
    }

    @Override
    public void run(RunNotifier notifier) {
        System.out.println("running the tests from MyRunner: " + testClass);
        try {
            Object testObject = testClass.newInstance();
            for (Method method : testClass.getMethods()) {
                if (method.isAnnotationPresent(Test.class)) {
                    notifier.fireTestStarted(Description
                      .createTestDescription(testClass, method.getName()));
                    method.invoke(testObject);
                    notifier.fireTestFinished(Description
                      .createTestDescription(testClass, method.getName()));
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

The getDescription method is inherited from Describable and returns a Description that contains the information that is later being exported and may be used by various tools.

In the run implementation, we’re invoking the target test methods using reflection.

We’ve defined a constructor that takes a Class argument; this is a JUnit’s requirement. At runtime, JUnit will pass the target test class to this constructor.

RunNotifier is used for firing events that have information about the test progress.

Let’s use the runner in our test class:

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

@RunWith(TestRunner.class)
public class CalculatorTest {
    Calculator calculator = new Calculator();

    @Test
    public void testAddition() {
        Syste.out.println("in testAddition");
        assertEquals("addition", 8, calculator.add(5, 3));
    }
}

The result we get:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.baeldung.junit.CalculatorTest
running the tests from MyRunner: class com.baeldung.junit.CalculatorTest
in testAddition
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

4. Specialized Runners

Instead of extending the low-level Runner class, as we did in the last example, we can extend one of the specialized subclasses of Runner: ParentRunner or BlockJUnit4Runner.

The abstract ParentRunner class runs the tests in a hierarchical manner.

BlockJUnit4Runner is a concrete class and if we prefer to customize certain methods, we’ll probably be extending this class.

Let’s see that with an example:

public class BlockingTestRunner extends BlockJUnit4ClassRunner {
    public BlockingTestRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected Statement methodInvoker(FrameworkMethod method, Object test) {
        System.out.println("invoking: " + method.getName());
        return super.methodInvoker(method, test);
    }
}

Annotating a class with @RunWith(JUnit4.class) will always invoke the default JUnit 4 runner in the current version of JUnit; this class aliases the current default JUnit 4 class runner:

@RunWith(JUnit4.class)
public class CalculatorTest {
    Calculator calculator = new Calculator();

    @Test
    public void testAddition() {
        assertEquals("addition", 8, calculator.add(5, 3));
    }
}

5. Conclusion

JUnit Runners are highly adaptable and let the developer change the test execution procedure and the whole test process.

If we only want to make minor changes it is a good idea to have a look at the protected methods of BlockJUnit4Class runner.

Some popular third-party implementations of runners for use include SpringJUnit4ClassRunner, MockitoJUnitRunner, HierarchicalContextRunner, Cucumber Runner and much more.

The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.

Leave a Reply

Your email address will not be published.