On this page
JUnit 5 Basics
JUnit 5 (Jupiter) is the modern testing framework for Java. It replaces JUnit 4 with a modular architecture, richer annotations, and improved assertions.
Dependencies
Maven:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
Gradle:
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
tasks.test { useJUnitPlatform() }
Basic Test
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void shouldAddTwoNumbers() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
@Test
void shouldThrowOnDivisionByZero() {
Calculator calc = new Calculator();
assertThrows(ArithmeticException.class, () -> calc.divide(10, 0));
}
}
Common Assertions
assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(value);
assertNotNull(value);
assertSame(expected, actual); // same reference
assertNotSame(unexpected, actual);
assertThrows(Exception.class, () -> { /* code */ });
assertTimeout(Duration.ofSeconds(1), () -> { /* code */ });
assertAll("person",
() -> assertEquals("Alice", person.getName()),
() -> assertEquals(30, person.getAge())
);
Test Lifecycle Annotations
class UserServiceTest {
@BeforeAll
static void setupClass() {
// runs once before all tests — must be static
}
@BeforeEach
void setup() {
// runs before each test
}
@AfterEach
void teardown() {
// runs after each test
}
@AfterAll
static void teardownClass() {
// runs once after all tests
}
@Test
void shouldCreateUser() { }
}
Display Names and Tags
@DisplayName("User Service Tests")
class UserServiceTest {
@Test
@DisplayName("Should create user with valid email")
void createUser() { }
@Test
@Tag("integration")
void integrationTest() { }
@Test
@Disabled("Not implemented yet")
void futureFeature() { }
}
Run by tag:
mvn test -Dgroups=integration
./gradlew test --tests "*" -Djunit.jupiter.tags=integration
Assumptions
Skip tests when preconditions are not met:
@Test
void runOnlyOnLinux() {
assumeTrue(System.getProperty("os.name").contains("Linux"));
// test logic runs only if assumption passes
}
Nested Tests
@DisplayName("Shopping Cart")
class ShoppingCartTest {
@Nested
@DisplayName("When empty")
class WhenEmpty {
@Test
void shouldHaveZeroItems() { }
}
@Nested
@DisplayName("When has items")
class WhenHasItems {
@BeforeEach
void addItem() { /* setup */ }
@Test
void shouldCalculateTotal() { }
}
}
Best Practices
- One logical assertion per test (or use
assertAll) - Follow naming:
shouldDoSomethingWhenConditionormethodName_scenario_expectedResult - Use
@BeforeEachfor per-test setup, not shared mutable state between tests - Keep tests fast, independent, and repeatable
- Use
@DisplayNamefor readable test reports