JUnit 5 (JUnit Jupiter) is the standard test framework for Java developers and is used in many API and unit-test suites alongside or instead of TestNG. This tutorial covers setup, the annotations, assertions, data-driven parameterized tests, and the features you'll use to organize a suite.
JUnit 5 Annotations
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CalculatorTest {
Calculator calc;
@BeforeAll // runs ONCE before all tests in class (static)
static void initAll() { System.out.println("Before all tests"); }
@BeforeEach // runs before EACH test method
void setUp() { calc = new Calculator(); }
@Test
@Order(1)
@DisplayName("Addition should work correctly")
void testAdd() {
assertEquals(5, calc.add(2, 3));
assertEquals(-1, calc.add(2, -3));
}
@Test
@Order(2)
void testDivideByZero() {
// assertThrows — expect exception
ArithmeticException ex = assertThrows(
ArithmeticException.class,
() -> calc.divide(10, 0));
assertEquals("/ by zero", ex.getMessage());
}
@Test
@Disabled("Not implemented yet")
void testNotYetReady() { }
// ── PARAMETERIZED TESTS ──
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testIsPositive(int number) {
assertTrue(number > 0);
}
@ParameterizedTest
@CsvSource({
"2, 3, 5",
"10, 20, 30",
"-5, 5, 0"
})
void testAddCsv(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
@ParameterizedTest
@CsvFileSource(resources = "/testdata.csv", numLinesToSkip = 1)
void testFromFile(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
@ParameterizedTest
@MethodSource("addProvider")
void testAddMethod(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
static Stream<Arguments> addProvider() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(5, 5, 10)
);
}
// ── ASSERTIONS ──
@Test
void allAssertions() {
assertEquals(5, calc.add(2,3));
assertNotEquals(6, calc.add(2,3));
assertTrue(calc.add(1,1) > 0);
assertFalse(calc.add(-1,-1) > 0);
assertNull(calc.getValue());
assertNotNull(calc);
// assertAll — run all, report all failures
assertAll("multiple assertions",
() -> assertEquals(5, calc.add(2,3)),
() -> assertTrue(calc.add(1,1) == 2),
() -> assertNotNull(calc)
);
// assertTimeout
assertTimeout(Duration.ofSeconds(1),
() -> calc.add(1, 2));
}
@AfterEach // runs after EACH test
void tearDown() { calc = null; }
@AfterAll // runs ONCE after all tests (static)
static void cleanAll() { System.out.println("After all tests"); }
// ── NESTED TESTS ──
@Nested
@DisplayName("Edge cases")
class EdgeCases {
@Test
void testMaxInt() {
assertEquals(Integer.MAX_VALUE, calc.add(Integer.MAX_VALUE, 0));
}
}
}
Maven Setup
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.0</version> <!-- check Maven Central for the latest 5.x -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <!-- API, engine and params -->
<scope>test</scope>
</dependency>
</dependencies>
Maven Surefire 3.x discovers and runs JUnit 5 tests automatically; mvn test is all you need.
Lifecycle Annotations at a Glance
| Annotation | Runs | Note |
|---|---|---|
@BeforeAll / @AfterAll | Once per class | Must be static unless you use @TestInstance(Lifecycle.PER_CLASS) |
@BeforeEach / @AfterEach | Around every test | Fresh setup per test (a new instance is created per test by default) |
@Test | The test itself | Methods don't need to be public |
@DisplayName | — | Readable name in reports |
@Disabled | Skipped | Always give a reason |
@Tag("smoke") | — | Filter with mvn test -Dgroups=smoke |
@Nested | — | Group related tests in inner classes |
@Timeout(5) | — | Fails the test if it runs longer than 5 seconds |
Grouped Assertions and Parallel Runs
@Test
void profileIsComplete() {
User u = api.getUser(7);
assertAll("user 7", // reports every failure, not just the first
() -> assertEquals("Asha", u.name()),
() -> assertTrue(u.email().endsWith("@example.com")),
() -> assertNotNull(u.createdAt()));
}
# src/test/resources/junit-platform.properties
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
For Selenium tests run in parallel, keep one WebDriver per thread exactly as with TestNG. Coming from TestNG? See TestNG vs JUnit 5 for the annotation equivalents.
FAQs
What is the difference between JUnit 4 and JUnit 5?
JUnit 5 is a rewrite with a new package (org.junit.jupiter), new annotations (@BeforeEach instead of @Before), an extension model replacing runners and rules, built-in parameterized tests, nested tests and tags.
Why must @BeforeAll be static in JUnit 5?
Because JUnit creates a new test-class instance for each test by default, so a once-per-class method can't belong to an instance. Annotate the class with @TestInstance(Lifecycle.PER_CLASS) to allow non-static methods.
How do you run JUnit 5 tests by tag?
Annotate tests with @Tag("smoke") and run mvn test -Dgroups=smoke, or configure groups and excludedGroups in the Surefire plugin.