Lesson 30 / 38

JUnit 5 In Depth

Use the full assertion toolkit — assertEquals, assertThrows, assertAll — and control setup/teardown per test and per class with JUnit 5's lifecycle annotations.

Lifecycle annotations

@BeforeEach/@AfterEach run around every test method (fresh state, no leakage between tests); @BeforeAll/@AfterAll run once for the whole class and must be static (unless the class uses @TestInstance(PER_CLASS)). Use @BeforeAll only for expensive, read-only setup shared safely across tests.

assertAll and assertThrows

assertAll groups related assertions so all of them run and report even if an earlier one fails — a single assertEquals would stop at the first failure. assertThrows verifies a specific exception type and lets you inspect its message.

@Test
void orderValidation() {
    Order o = new Order(-5, "");
    assertAll(
        () -> assertTrue(o.hasErrors()),
        () -> assertEquals(2, o.errorCount())
    );

    var ex = assertThrows(IllegalArgumentException.class,
        () -> new Order(-1, "x"));
    assertTrue(ex.getMessage().contains("quantity"));
}

Parameterized tests

@ParameterizedTest with @ValueSource, @CsvSource, or @MethodSource runs the same test body once per input, replacing copy-pasted near-identical @Test methods.

@ParameterizedTest
@CsvSource({"2,4", "3,9", "5,25"})
void squares(int in, int expected) {
    assertEquals(expected, MathUtil.square(in));
}