Labs ICT
โญ Pro Login

JUnit 5 Annotations Overview

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
}
    

๐Ÿงช Quick Quiz

How do you disable a test in JUnit 5?