W3docs

Java JUnit Annotations

Core JUnit 5 annotations — @Test, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll, @Disabled.

Java JUnit Annotations

JUnit 5 is the de-facto standard test framework for Java, and almost everything you tell it happens through annotations. You do not write a main method or call methods yourself; you decorate ordinary methods with annotations like @Test, @BeforeEach, and @AfterAll, and the JUnit engine discovers them by reflection and runs them in the right order. This chapter covers the core lifecycle annotations — what each one means, when it fires, and how they combine to give every test a clean, isolated fixture.

The annotations live in org.junit.jupiter.api

JUnit 5's API is the Jupiter module. The annotations you reach for daily all come from one package:

AnnotationApplies toRuns
@Testa methodonce per test method
@BeforeEacha methodbefore every @Test
@AfterEacha methodafter every @Test
@BeforeAlla static methodonce, before any test in the class
@AfterAlla static methodonce, after all tests in the class
@Disableda method or classnever (it is skipped and reported)
@DisplayNamea method or classsets a human-readable name in reports

A method marked @Test needs no public modifier in JUnit 5 (package-private is fine) and must return void.

@Test: the unit of work

A test method asserts something with the static helpers in org.junit.jupiter.api.Assertions. If an assertion fails it throws, and the engine records that one test as failed without stopping the others.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    void addsTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    void throwsOnDivideByZero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
    }
}

Each @Test runs on a fresh instance of the test class — JUnit constructs a new object per test by default, so fields set in one test cannot leak into another.

@BeforeEach and @AfterEach: per-test fixtures

Setup that every test needs goes in a @BeforeEach method; teardown goes in @AfterEach. They bracket each @Test, giving every test an identical starting point.

import org.junit.jupiter.api.*;

class OrderServiceTest {

    private OrderService service;

    @BeforeEach
    void setUp() {
        service = new OrderService(new InMemoryRepo()); // fresh state per test
    }

    @AfterEach
    void tearDown() {
        service.close(); // runs even if the test threw
    }

    @Test
    void placesOrder() {
        assertTrue(service.place("SKU-1", 2));
    }
}

@AfterEach runs even when the test fails, which makes it the right place to release resources you opened in @BeforeEach.

@BeforeAll and @AfterAll: once per class

When setup is expensive and shareable — a database container, a started embedded server — use @BeforeAll to do it once and @AfterAll to tear it down once. Because they run before any instance exists, they must be static.

import org.junit.jupiter.api.*;

class RepositoryTest {

    static Database db;

    @BeforeAll
    static void startDatabase() {
        db = Database.start();   // runs once, before everything
    }

    @AfterAll
    static void stopDatabase() {
        db.stop();               // runs once, after everything
    }

    @Test
    void savesRow() {
        assertEquals(1, db.insert("hello"));
    }
}

The full lifecycle order for a class with two tests is: @BeforeAll → (@BeforeEach@Test@AfterEach) → (@BeforeEach@Test@AfterEach) → @AfterAll.

@Disabled: skip without deleting

@Disabled turns a test (or a whole class) off. The engine reports it as skipped rather than passed or failed, so it stays visible. Always give a reason.

@Test
@Disabled("flaky until the rate-limiter fix lands — see JIRA-1234")
void callsExternalApi() {
    // not executed
}

A worked example: a mini test engine

There is no JUnit jar on this runner, so the program below builds a tiny engine of its own with the exact same shape as JUnit. It declares marker annotations (@BeforeAll, @BeforeEach, @Test, @AfterEach, @AfterAll, @Disabled), defines a small annotated test class, then uses reflection — precisely what JUnit's engine does internally — to discover and run the methods in lifecycle order and print a pass/fail/skip summary.

java— editable, runs on the server

What to take from the run:

  • @BeforeAll printed exactly once at the very top and @AfterAll exactly once at the very bottom — the class-level setup and teardown bracket the entire run, which is why JUnit requires them to be static.
  • Every executed @Test is preceded by a @BeforeEach line and followed by an @AfterEach line, so each test ran against a freshly prepared fixture and cleaned up after itself — the per-test bracketing that keeps tests independent.
  • The flaky method carried @Disabled, so it printed (skipped via @Disabled) and its body never ran; the failing assertion inside it was never reached, which is the whole point of disabling instead of deleting.
  • The discovery loop only acts on methods where isAnnotationPresent(Test.class) is true — annotations are just metadata, and it is the engine reading them via reflection that turns them into behaviour, exactly how real JUnit works.
  • The final line reports 2 passed, 0 failed, 1 skipped: two real tests passed, none failed, and the disabled one was counted as skipped rather than silently ignored — the same pass/fail/skip accounting a JUnit report gives you.

Practice

Practice

In JUnit 5, why must a method annotated with @BeforeAll be declared static?