If a JUnit class or its parent class is annotated with @RunWith, JUnit framework invokes the specified class as a test runner instead of running the default runner.
@RunWith has only one element as shown:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface RunWith {
Class<? extends Runner> value();
}
The specified 'value' element must be a subclass of the abstract org.junit.runner.Runner class. A Runner class is responsible to run JUnit test, typically by reflection.
An example of @RunWith is @RunWith(Suite.class) which specifies a group of many test classes to run along with the class where it is used.
In the following examples, we will learn how to write our own custom Runner and invoke it with @RunWith. These examples will only give a high-level understanding of the concept and should be considered starting point to implement a custom Runner.
Implementing Runner
Here we are implementing abstract methods of Runner class. In run() method, we are invoking the target test methods by reflection.
public class MyRunner extends Runner {
private Class testClass;
public MyRunner(Class testClass) {
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.EMPTY);
method.invoke(testObject);
notifier.fireTestFinished(Description.EMPTY);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Note that, we have defined a constructor that takes a Class argument. This is JUnit requirement. During runtime JUnit will pass the target test class to this constructor.
RunNotifier is used to fire events to notify the events which the JUnit core layer listens to. These events has information about the test progress.
Let's use the runner in our test class:
@RunWith(MyRunner.class)
public class MyTest {
@BeforeClass
public static void beforeClass() {
System.out.println("in beforeClass method");
}
@Before
public void before() {
System.out.println("in before method");
}
@Test
public void testMethod1() {
System.out.println("in the testMethod1");
}
@Test
public void testMethod2() {
System.out.println("in the testMethod2");
}
} mvn -q test -Dtest=MyTest Outputd:\example-projects\junit\junit-run-with>mvn -q test -Dtest=MyTest
------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.logicbig.example.MyTest running the tests from MyRunner. class com.logicbig.example.MyTest in the testMethod2 in the testMethod1 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
In above output, the lifecycle methods @BeforeClass and @Before were not invoked. That's our responsibility to invoked these lifecycle methods, just like we invoked @Test methods.
Also it's important to understand that a Runner is invoked by JUnitCore, that means we cannot use JUnitCore.runClasses or similar methods inside Runner#run method, otherwise, that will cause recursive run method calls.
Other Runner classes
|
Instead of extending the low level Runner class, as we did in the last example, we should be extending one of the specialized subclasses of Runner : ParentRunner or BlockJUnit4Runner. The abstract ParentRunner runs the tests in a hierarchical manner. BlockJUnit4Runner is the concrete class. If no @RunWith is specified, then this is the one which runs by default. We will probably be extending this class to customize certain methods. Let's see that with an example. |
Extending BlockJUnit4ClassRunner
public class MyRunner2 extends BlockJUnit4ClassRunner {
public MyRunner2(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
System.out.println("invoking: " + method.toString());
return super.methodInvoker(method, test);
}
}
@RunWith(MyRunner2.class)
public class MyTest2 {
@BeforeClass
public static void beforeClass() {
System.out.println("in beforeClass method");
}
@Before
public void before() {
System.out.println("in before method");
}
@Test
public void testMethod1() {
System.out.println("in the testMethod1");
}
@Test
public void testMethod2() {
System.out.println("in the testMethod2");
}
} mvn -q test -Dtest=MyTest2 Outputd:\example-projects\junit\junit-run-with>mvn -q test -Dtest=MyTest2
------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.logicbig.example.MyTest2 in beforeClass method invoking: public void com.logicbig.example.MyTest2.testMethod1() in before method in the testMethod1 invoking: public void com.logicbig.example.MyTest2.testMethod2() in before method in the testMethod2 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
Above output shows that life-cycle methods, @BeforeClass/@Before, were also called.
Example ProjectDependencies and Technologies Used: - junit 4.12: JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
- JDK 1.8
- Maven 3.3.9
|