JUnit 5 has a rich set of annotations that give you fine-grained control over your tests. In this lesson, we will go through every important annotation so you have a complete reference. Think of this as your cheat sheet.
Complete Annotations Reference
+---------------------------+---------------------------------------------+
| Annotation | Purpose |
+---------------------------+---------------------------------------------+
| @Test | Marks a method as a test |
| @BeforeEach | Runs before each test method |
| @AfterEach | Runs after each test method |
| @BeforeAll | Runs once before all tests in class |
| @AfterAll | Runs once after all tests in class |
| @DisplayName | Custom display name for test |
| @Nested | Groups tests into inner classes |
| @Tag | Categorizes tests for filtering |
| @Disabled | Skips the test |
| @Timeout | Sets a time limit for execution |
| @ExtendWith | Registers extensions |
| @ParameterizedTest | Runs test with different arguments |
| @RepeatedTest | Repeats the test multiple times |
| @TestFactory | Creates dynamic tests |
| @TestInstance | Controls test lifecycle |
| @TestMethodOrder | Controls test execution order |
+---------------------------+---------------------------------------------+
@Test - The Basics
import org.junit.jupiter.api.Test;
class MathTest {
@Test
void addition() {
assertEquals(4, 2 + 2);
}
// @Test can have optional attributes:
@Test
void withTimeout() {
// This test must complete within 1 second
// (though @Timeout annotation is preferred)
}
}
@DisplayName - Human-Readable Names
@Test
@DisplayName("Should calculate factorial of 5 correctly")
void factorialTest() {
assertEquals(120, math.factorial(5));
}
@DisplayName("User Registration")
class UserRegistrationTest {
@Test
@DisplayName("should register with valid email")
void validEmail() {
// ...
}
@Test
@DisplayName("should reject invalid email format")
void invalidEmail() {
// ...
}
}
@Nested - Organizing Tests
class ShoppingCartTest {
ShoppingCart cart = new ShoppingCart();
@Nested
@DisplayName("when cart is empty")
class EmptyCart {
@Test
@DisplayName("total should be zero")
void totalIsZero() {
assertEquals(0, cart.getTotal());
}
@Test
@DisplayName("item count should be zero")
void itemCountIsZero() {
assertEquals(0, cart.getItemCount());
}
}
@Nested
@DisplayName("after adding an item")
class AfterAddingItem {
@BeforeEach
void addItem() {
cart.addItem(new Item("Widget", 9.99));
}
@Test
@DisplayName("total should match item price")
void totalMatchesItemPrice() {
assertEquals(9.99, cart.getTotal());
}
}
}
@TestInstance - Lifecycle Control
By default, JUnit creates a new instance of the test class for each test method (PER_METHOD). You can change this to PER_CLASS:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StatefulTest {
private int counter = 0;
@Test
void firstTest() {
counter++;
assertEquals(1, counter);
}
@Test
void secondTest() {
counter++;
assertEquals(2, counter);
}
}
With PER_CLASS, the same instance is used for all tests. This means
@BeforeAll and @AfterAll no longer need to be static.
But be careful โ tests should generally be independent, so shared state can be risky.
@TestMethodOrder - Controlling Execution Order
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTest {
@Test
@Order(1)
void first() {
// Runs first
}
@Test
@Order(2)
void second() {
// Runs second
}
@Test
@Order(3)
void third() {
// Runs third
}
}
Other ordering strategies include MethodOrderer.Alphanumeric,
MethodOrderer.Random, and MethodOrderer.OrderAnnotation.
@RepeatedTest
@RepeatedTest(5)
void repeatedTest() {
// This test runs 5 times
}
@RepeatedTest(value = 3, name = "Run {currentRepetition}/{totalRepetition}")
void namedRepeatedTest(RepetitionInfo info) {
System.out.println("Repetition: " + info.getCurrentRepetition());
}
@ExtendWith - Registering Extensions
@ExtendWith(MyCustomExtension.class)
class ExtendedTest {
// This test class uses MyCustomExtension
}
// Multiple extensions
@ExtendWith({ExtensionA.class, ExtensionB.class})
class MultiExtensionTest {
// Uses both extensions
}