JUnit5 is the latest version of JUnit. Many new JUnit5 annotations are introduced like @Disabled, @Tag and many annotations of JUnit4 no longer exist like @After, @Before, @Category, and more. In this blog, we will highlight some of the new JUnit5 annotations. If you are new to JUnit 5, you may refer to our blog “What is JUnit5.”
1. @Test
This annotation denotes a method to be a test method. Also, there is no need for any attribute, unlike JUnit 4 @Test annotation.
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class FirstJUnit5Tests {
@Test
void myFirstTest() {
assertEquals(2, 1 + 1);
}
}
2. @ParameterizedTest
This annotation denotes an annotated method as a parameterized test method. It allows running a test multiple times with different arguments.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class JUnit5Test {
@ParameterizedTest
@ValueSource(strings = { "call", "call2", "call3" })
void endsWithI(String str) {
assertTrue(str.endsWith("i"));
}
}
3. @RepeatedTest
This annotation denotes an annotated method as a test template method. It allows a test to repeat a specified number of times. Just by declaring a method with @RepeatedTest annotation and specifying the number of times the method to repeat. Also, every invocation of @RepeatedTest behaves like execution of @Test annotation. We cannot annotate private or static methods as a @RepeatedTest method and must return void.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;
import static org.junit.jupiter.api.Assertions.assertEquals;
class JUnit5Test {
@RepeatedTest(value = 5, name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("RepeatingTest")
void customDisplayName(RepetitionInfo repInfo, TestInfo testInfo) {
int i = 3;
System.out.println(testInfo.getDisplayName() +
"-->" + repInfo.getCurrentRepetition()
);
assertEquals(repInfo.getCurrentRepetition(), i);
}
}import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;
import static org.junit.jupiter.api.Assertions.assertEquals;
class JUnit5Test {
@RepeatedTest(value = 5, name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("RepeatingTest")
void customDisplayName(RepetitionInfo repInfo, TestInfo testInfo) {
int i = 3;
System.out.println(testInfo.getDisplayName() +
"-->" + repInfo.getCurrentRepetition()
);
assertEquals(repInfo.getCurrentRepetition(), i);
}
}
4. @TestFactory
This annotation denotes an annotated method as a test factory method. Unlike @Test, it is not a test case as it is a factory of test cases. We cannot annotate private or static methods as a @TestFactory method and must return collection, iterable, stream, and an array of a DynamicNode.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;
class DynamicTestsDemo {
// This will result in a JUnitException!
@TestFactory
List<String> dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello");
}
@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test", () -> assertTrue(true)),
dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2))
);
}
@TestFactory
Iterable<DynamicTest> dynamicTestsFromIterable() {
return Arrays.asList(
dynamicTest("3rd dynamic test", () -> assertTrue(true)),
dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2))
);
}
@TestFactory
Iterator<DynamicTest> dynamicTestsFromIterator() {
return Arrays.asList(
dynamicTest("5th dynamic test", () -> assertTrue(true)),
dynamicTest("6th dynamic test", () -> assertEquals(4, 2 * 2))
).iterator();
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
return Stream.of("A", "B", "C")
.map(str -> dynamicTest("test" + str, () -> { /* ... */ }));
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromIntStream() {
// Generates tests for the first 10 even integers.
return IntStream.iterate(0, n -> n + 2).limit(10)
.mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
}
@TestFactory
Stream<DynamicTest> generateRandomNumberOfTests() {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
Iterator<Integer> inputGenerator = new Iterator<Integer>() {
Random random = new Random();
int current;
@Override
public boolean hasNext() {
current = random.nextInt(100);
return current % 7 != 0;
}
@Override
public Integer next() {
return current;
}
};
// Generates display names like: input:5, input:37, input:85, etc.
Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;
// Executes tests based on the current input value.
ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
}
@TestFactory
Stream<DynamicNode> dynamicTestsWithContainers() {
return Stream.of("A", "B", "C")
.map(input -> dynamicContainer("Container " + input, Stream.of(
dynamicTest("not null", () -> assertNotNull(input)),
dynamicContainer("properties", Stream.of(
dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
))
)));
}
}
5. @DisplayName
This annotation declares custom names for test cases and methods used for test reports in IDEs. They can contain special characters, space, and emojis.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
@DisplayName("DisplayName 1")
class JUnit5Test {
@Test
@DisplayName("test name 1")
void testWithDisplayName() {
}
@Test
@DisplayName("test name 2")
void printDisplayName(TestInfo testInfo) {
System.out.println(testInfo.getDisplayName());
}
}
6. @BeforeEach
This annotation denotes an annotated method should execute before each @Test method for a given test case. We cannot annotate private or static methods as a @BeforeEach method and must have a void return type.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
@AfterEach
void tearDown() {
}
}
7. @AfterEach
In contrast to @BeforeEach, this annotation denotes an annotated method should execute after each @Test annotated method for a given test case. @AfterEach annotated method must not be private or static and must have a void return type.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
@AfterEach
void tearDown() {
}
}
8. @BeforeAll
This annotation denotes a method should execute before all @Test methods for a given test case. @BeforeAll methods must not be private or static and must have a void return type.
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeAll
static void initAll() {
}
@Test
void succeedingTest() {
}
@AfterAll
static void tearDownAll() {
}
}
9. @AfterAll
This annotation denotes that an annotated method should execute after all @Test annotated methods for a given test case. @AfterAll annotated methods must not be private or static and must have a void return type.
All the annotated @BeforeAll methods and @AfterAll methods are only executed once for a given test case.
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeAll
static void initAll() {
}
@Test
void succeedingTest() {
}
@AfterAll
static void tearDownAll() {
}
}
10. @Nested
This annotation denotes that an inner class to be a test class, i.e., an annotated class is a non-static nested test class. In nested class, @BeforeAll and @AfterAll annotated methods cannot be directly used unless the “pre-class” is used.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.EmptyStackException;
import java.util.Stack;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, () -> stack.pop());
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, () -> stack.peek());
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
11. @Tag
This annotation is a repeatable annotation declare a tag for the annotated test class. @Tag is used to filter the test. In addition, they help to create test packs with selected tests.
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("smoke")
class JUnit5Test {
@Test
@Tag("login")
void validLoginTest() {
}
@Test
@Tag("search")
void searchTest() {
}
}
12. @Disable
This annotation denotes that an annotated test class or a test method is disabled or ignored and should not executed. When declared at the class level, all @Test annotated methods are disabled automatically, while at the method level, only the annotated method is disabled.
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled
class DisabledClassDemo {
@Test
void testWillBeSkipped() {
}
}
13. @ExtendWith
This annotation is a repeatable annotation used to list custom extensions for the annotated test class or test method.
@ExtendWith(MyExtension.class)
@Test
void test() {
// ...
}
Conclusion
Moreover, with the benefits of new and improved JUnit5 annotations, JUnit 5 is more integrated with Java 8 features, especially lambda expressions. In the next blog, we will introduce assertions and assumptions in JUnit5.
Some links on this page are affiliate links. This means that if you choose to make a purchase, we may earn a small commission at no extra cost to you. For more information, Go here