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));
        }
    }
}
Advertisement

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

AnnotationRunsNote
@BeforeAll / @AfterAllOnce per classMust be static unless you use @TestInstance(Lifecycle.PER_CLASS)
@BeforeEach / @AfterEachAround every testFresh setup per test (a new instance is created per test by default)
@TestThe test itselfMethods don't need to be public
@DisplayName—Readable name in reports
@DisabledSkippedAlways 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.