You have seen the @Test annotation in action, but JUnit 5 has a whole
bunch of annotations that let you control how your tests run. Let us go through
the most important ones so you know what tools you have at your disposal.
The Core Annotations
import org.junit.jupiter.api.*;
class AnnotationsDemoTest {
@BeforeAll
static void beforeAll() {
System.out.println("Runs once before ALL tests");
}
@BeforeEach
void beforeEach() {
System.out.println("Runs before EACH test");
}
@Test
void firstTest() {
System.out.println("First test");
}
@Test
void secondTest() {
System.out.println("Second test");
}
@AfterEach
void afterEach() {
System.out.println("Runs after EACH test");
}
@AfterAll
static void afterAll() {
System.out.println("Runs once after ALL tests");
}
}
Running this prints:
Runs once before ALL tests
Runs before EACH test
First test
Runs after EACH test
Runs before EACH test
Second test
Runs after EACH test
Runs once after ALL tests
The @Test Annotation
This is the most basic annotation. Any method annotated with @Test
is a test method. That is it. No special naming conventions required (unlike JUnit 4
where methods had to start with "test").
@Test
void thisIsATest() {
// JUnit will find and run this method
}
@Test
void thisIsAlsoATest() {
// This one too
}
void thisIsNotATest() {
// No @Test annotation, so JUnit ignores this
}
@Disabled - Skipping Tests
Sometimes you want to skip a test. Maybe the feature is not implemented yet, or
there is a known bug you are not ready to fix. Use @Disabled:
@Test
@Disabled("Not implemented yet - waiting for API")
void shouldCallExternalService() {
// This test will be skipped
}
@Test
@Disabled("Known issue: see JIRA-1234")
void shouldHandleEdgeCase() {
// This test will also be skipped
}
The string argument is optional but highly recommended. It tells other developers why the test is disabled. A disabled test without a reason is like a TODO comment without context — useless.
@DisplayName - Making Tests Readable
By default, JUnit uses the method name as the display name. But method names have
limitations (no spaces, no special characters). @DisplayName lets you
give your tests a human-readable name:
@Test
@DisplayName("Should add two positive numbers correctly")
void shouldAddTwoPositiveNumbers() {
Calculator calc = new Calculator();
assertEquals(7, calc.add(3, 4));
}
@Test
@DisplayName("Should return 0 when adding zero")
void addWithZero() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(5, 0));
}
This shows up in your test reports and makes it much easier to understand what is being tested at a glance.
@Nested - Grouping Tests
When you have many tests for a single class, @Nested lets you group
them into logical sections using inner classes:
class CalculatorTest {
Calculator calc = new Calculator();
@Nested
class AdditionTests {
@Test
void shouldAddPositiveNumbers() {
assertEquals(5, calc.add(2, 3));
}
@Test
void shouldHandleNegativeNumbers() {
assertEquals(-1, calc.add(-3, 2));
}
}
@Nested
class DivisionTests {
@Test
void shouldDivideCorrectly() {
assertEquals(3, calc.divide(9, 3));
}
@Test
void shouldThrowWhenDividingByZero() {
assertThrows(ArithmeticException.class,
() -> calc.divide(1, 0));
}
}
}
Now your test report looks like a clear hierarchy instead of a flat list of 50 tests.
@Tag - Filtering Tests
Tags let you categorize tests so you can run subsets:
@Test
@Tag("fast")
void quickTest() {
// Runs quickly
}
@Test
@Tag("slow")
void databaseTest() {
// Takes time, maybe skip in CI
}
@Test
@Tag("integration")
void externalApiTest() {
// Depends on external service
}
Run only fast tests:
# Maven
mvn test -Dgroups=fast
# Gradle
gradle test -Dgroups=fast
# Exclude slow tests
mvn test -DexcludedGroups=slow