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:
| Annotation | Applies to | Runs |
|---|---|---|
@Test | a method | once per test method |
@BeforeEach | a method | before every @Test |
@AfterEach | a method | after every @Test |
@BeforeAll | a static method | once, before any test in the class |
@AfterAll | a static method | once, after all tests in the class |
@Disabled | a method or class | never (it is skipped and reported) |
@DisplayName | a method or class | sets 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.
What to take from the run:
@BeforeAllprinted exactly once at the very top and@AfterAllexactly once at the very bottom — the class-level setup and teardown bracket the entire run, which is why JUnit requires them to bestatic.- Every executed
@Testis preceded by a@BeforeEachline and followed by an@AfterEachline, so each test ran against a freshly prepared fixture and cleaned up after itself — the per-test bracketing that keeps tests independent. - The
flakymethod 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
In JUnit 5, why must a method annotated with @BeforeAll be declared static?