Master the most popular testing framework for Java, through the Learn JUnit course:
Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
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.
