Amazing Features In JUnit 5
1. Display Name Generators
JUnit 5 has support for customizing test class and test method names. We can use JUnit 5 custom display name generators via the @DisplayNameGeneration annotation. To get started, JUnit 5 provides a ReplaceUnderscores class that replaces any underscores in names with spaces. However, the @DisplayName annotation takes precedence over any display name generator.
@DisplayNameGeneration(ReplaceUnderscores.class)
class This_is_a_test_class {
@Test
void divisible_by_zero() {
assertEquals(42 % 2, 0);
}
@Test
@DisplayName("this is a negative check test")
void is_negative() {
assertTrue(-1 < 0);
}
}
When we run the test, we can see that the output is more readable.
2. Parallel Execution
Parallel test execution is an experimental feature and is available as an opt-in since version 5.3.
To enable parallel execution, set the following configuration parameters in junit-platform.properties
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
Synchronization: JUnit 5 provides another annotation-based declarative synchronization mechanism. The @ResourceLock annotation allows you to declare that a test class or method uses a specific shared resource that requires synchronized access to ensure reliable test execution. The shared resource is identified by a unique name which is a String.
class SharedResourceTest {
List<String> resources = new ArrayList<>();
@Test
@ResourceLock(value = "resources")
void first() throws Exception {
System.out.println("SharedResourceTest first() start => " + Thread.currentThread().getName());
resources.add("first");
Thread.sleep(500);
assertEquals(1, resources.size());
System.out.println("SharedResourceTest first() end => " + Thread.currentThread().getName());
}
@Test
@ResourceLock(value = "resources")
void second() throws Exception {
System.out.println("SharedResourceTest second() start => " + Thread.currentThread().getName());
resources.add("second");
Thread.sleep(500);
assertEquals(1, resources.size());
System.out.println("SharedResourceTest second() end => " + Thread.currentThread().getName());
}
}
log output:
SharedResourceTest second() start => ForkJoinPool-1-worker-2
SharedResourceTest second() end => ForkJoinPool-1-worker-2
SharedResourceTest first() start => ForkJoinPool-1-worker-3
SharedResourceTest first() end => ForkJoinPool-1-worker-3
3. Parameterized Tests
class SquareRootTest {
@ParameterizedTest(name = "simple sqrt test: sqrt({0}) = sqrt({1})")
@CsvSource(textBlock = """
4.0, 2.0
9.0, 3.0
16.0, 4.0
""")
void sqrt(double input, double expected) {
assertEquals(Math.sqrt(input), expected, 0.01);
}
}
4. Repeated Test
When this test is run once or twice, it will pass without any issue. With the @RepeatedTest annotation, we can run it multiple times to make it fail. If you have code that you are unsure if there are any concurrency issues, this may be a way of testing it.
class ExampleRepeatedTest {
@RepeatedTest(100)
void flakyTest() {
assertEquals(0.0, Math.random(), 0.9);
}
}
5. Dynamic Test
Using the @TestFactory annotation, we can dynamically test all even numbers.
class ExampleDynamicTest {
@TestFactory
Stream<DynamicTest> evenNumbersAreDivisibleByTwo() {
return IntStream.iterate(0, n -> n + 2)
.limit(1_000)
.mapToObj(n -> dynamicTest(n + " is divisible by 2",
() -> assertEquals(0, n % 2)));
}
}