DS DevShelfHub Projects · AI tools
Cheatsheets / JUnit 5
Cheatsheet · Dev tooling

JUnit 5: Annotations, Lifecycle, Assertions and Parameterized Tests Reference Guide

By DevShelfHub

Jupiter annotations, lifecycle, assertions, parameterized, dynamic tests, extensions, conditional execution, Maven/Gradle — JUnit 5 surface.

118 items 8 min Jupiter Annotations Extensions

Start hereQuick start · 6 you’ll reach for daily

Mark a test@Test
Per-test setup@BeforeEach
AssertassertEquals(a, b)
Expect throwassertThrows(E.class, () -> ...)
Parameterize@ParameterizedTest @CsvSource
Runmvn test / gradle test

Target versions · paceVersions

Targets: junit-jupiter ≥ 5.11 java ≥ 17 maven-surefire ≥ 3.5 gradle ≥ 8.5

"JUnit 5" is actually three modules: Jupiter (the API + engine you write tests against), Platform (the runner Maven/Gradle invoke), and Vintage (a shim that runs JUnit 3/4 tests on the same platform). Almost all imports come from org.junit.jupiter.api.*. Use the BOM (org.junit:junit-bom) to align versions across artifacts.

Maven · Gradle · runSetup

bash


  
    org.junit.jupiter
    junit-jupiter            
    5.11.0
    test
  



  
    
      maven-surefire-plugin  
      3.5.0
    
    
      maven-failsafe-plugin  
      3.5.0
    
  



plugins { `java-library` }
dependencies {
    testImplementation(platform("org.junit:junit-bom:5.11.0"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.junit.jupiter:junit-jupiter-params")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test { useJUnitPlatform() }

# Run
mvn test                                 # all tests
mvn -Dtest=UserServiceTest test          # one class
mvn -Dtest='UserServiceTest#testLogin*' test
mvn -Dgroups="fast" test                 # @Tag filter (since 4.7.1)
gradle test --tests com.x.UserServiceTest
gradle test --tests "*Login*" -i        # filter + info logs

@Test · lifecycle · metaAnnotations

@TestMarks a method as a test. Returns void. No public required.
@DisplayName("a friendlier name")Custom name in reports + IDE.
@BeforeEach / @AfterEachRun before / after each test. Reset state here.
@BeforeAll / @AfterAllRun once per class. Must be static unless @TestInstance(PER_CLASS).
@TestInstance(Lifecycle.PER_CLASS)One instance for all tests in the class — lets @BeforeAll be non-static.
@Disabled / @Disabled("flaky — TICK-123")Skip a test or class with a reason.
@Tag("smoke") / @Tag("slow")Filter via -Dgroups / --tags.
@RepeatedTest(3)Run the same test N times.
@Timeout(value = 2, unit = TimeUnit.SECONDS)Fail the test if it exceeds the budget.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)Pin execution order with @Order(1). Avoid — tests should be independent.
@Nested class Inner { @Test ... }Group tests with shared state. Inner runs after outer @BeforeEach.

Assertions.* · assertAllAssertions

import static org.junit.jupiter.api.Assertions.*;Bring every assertion into scope.
assertEquals(expected, actual)Strict equality. expected first.
assertEquals(expected, actual, "msg" or () -> "msg")Lambda message lazy-evaluates only on fail.
assertNotEquals / assertSame / assertNotSameNegation / reference identity.
assertNull / assertNotNullNull checks.
assertTrue(cond) / assertFalse(cond)Boolean.
assertArrayEquals(expected, actual)Element-wise.
assertIterableEquals(a, b) / assertLinesMatch(expected, actual)Collections; LinesMatch supports regex per line.
assertThrows(IOException.class, () -> risky())Catches + returns the exception so you can inspect it.
assertDoesNotThrow(() -> safe())Pass if no throw.
assertTimeout(Duration.ofSeconds(2), () -> ...)Run in current thread; fails after the budget.
assertTimeoutPreemptively(...)Runs in a separate thread — interrupts if over budget.
assertAll("group", () -> ..., () -> ...)Preferred Run all assertions, report every failure (not just the first).
fail("explain why")Force a failure.
Need fluent matchers (assertThat(x, containsString("a")))? Pair JUnit with AssertJ — vanilla Jupiter intentionally keeps the API small.

conditional skip vs. failAssumptions

import static org.junit.jupiter.api.Assumptions.*;Assumption API — aborts rather than fails.
assumeTrue(condition)Abort with status aborted if false. Rest of test skipped.
assumingThat(cond, () -> { ... })Run a block only if cond — rest of test still runs.
@EnabledOnOs(LINUX, MAC) / @DisabledOnOs(WINDOWS)Skip by OS.
@EnabledOnJre(JAVA_21) / @DisabledOnJreSkip by JRE version.
@EnabledIfEnvironmentVariable(named="CI", matches="true")CI-only or local-only tests.
@EnabledIfSystemProperty(named="db", matches="postgres")Branch on JVM system property.
@EnabledIf("methodName")Custom predicate — static method returning boolean.

@ParameterizedTest · @TestFactoryParameterized & dynamic tests

@ParameterizedTestMark instead of @Test; needs a source.
@ValueSource(ints/strings/longs/…)Inline list of single arguments.
@CsvSource({"1,2,3"})Multi-arg rows. Supports nullable empty strings, quoting.
@CsvFileSource(resources = "/cases.csv", numLinesToSkip = 1)External CSV on the classpath.
@EnumSource(Day.class) / @EnumSource(value=Day.class, names={"SAT"})Enum values, all or filtered.
@MethodSource("provider")Most flexible Static method returns Stream<Arguments>.
@ArgumentsSource(MyProvider.class)Pluggable provider class.
@FieldSource("fieldName") 5.11+Read a static field of type Iterable/Stream.
@TestFactory
Stream<DynamicTest> tests() { ... }
Generate tests at runtime — one per element.
dynamicTest("name", () -> { ... })Build a DynamicTest from a name + executable.
@DisplayNameGeneration(ReplaceUnderscores.class)Auto-derive readable names from snake_case_method names.
javascript
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;

class CalcTest {

    // 1. Inline values
    @ParameterizedTest
    @ValueSource(ints = {1, 2, 4, 8})
    void isEven(int n) {
        // pytest-style — name shows the arg
    }

    // 2. Multi-arg from CSV
    @ParameterizedTest(name = "[{index}] {0} + {1} = {2}")
    @CsvSource({
        "1, 2, 3",
        "5, 7, 12",
        "'',  0, 0",                              // empty string is null/0
    })
    void add(int a, int b, int expected) {
        assertEquals(expected, Math.addExact(a, b));
    }

    // 3. From a CSV file on classpath
    @ParameterizedTest
    @CsvFileSource(resources = "/cases.csv", numLinesToSkip = 1)
    void fromFile(String input, String expected) {}

    // 4. Enum source — all values or a subset
    @ParameterizedTest
    @EnumSource(value = Day.class, names = {"SAT", "SUN"})
    void weekend(Day d) {}

    // 5. Method source — most flexible
    @ParameterizedTest
    @MethodSource("usersWithExpectedRoles")
    void rolesProject(User u, Role expected) {}

    static Stream usersWithExpectedRoles() {
        return Stream.of(
            Arguments.of(new User("alice"), Role.ADMIN),
            Arguments.of(new User("bob"),   Role.MEMBER)
        );
    }

    // 6. Dynamic tests — generate at runtime
    @TestFactory
    Stream evenNumbers() {
        return Stream.of(2, 4, 6).map(n -> dynamicTest("is " + n + " even", () -> {
            assertEquals(0, n % 2);
        }));
    }
}

when each hook runsLifecycle & ordering

@BeforeAll (static)Once before all tests in the class.
@BeforeEach (outer)Before each test — including nested. Outer runs first.
@BeforeEach (nested)Then the nested class’s.
test bodyThe test method.
@AfterEach (nested) → (outer)Reverse order — inner first.
@AfterAll (static)Once after every test.
@TestInstance(PER_CLASS)One instance shared across tests. @BeforeAll can be non-static.
@TestInstance(PER_METHOD) (default)Fresh instance per test. State in fields resets automatically.
junit.jupiter.execution.parallel.enabled = trueParallel Run tests concurrently. Configure in junit-platform.properties.
@Execution(CONCURRENT) / SAME_THREADPer-class / method override.
@ResourceLock("db", mode = READ_WRITE)Serialise tests that touch the same resource.

@ExtendWith · StoreExtensions

@ExtendWith(SpringExtension.class)Attach an extension to a class or test.
@RegisterExtension static MyExt ext = new MyExt(opt);Programmatic registration — lets you pass constructor args.
BeforeAll / AfterAll / BeforeEach / AfterEach CallbackLifecycle hooks — interfaces extensions implement.
BeforeTestExecution / AfterTestExecution CallbackTighter window — excludes @BeforeEach work.
ParameterResolverInject extra params into test methods (mocks, fixtures).
TestExecutionExceptionHandlerIntercept failures — swallow, rethrow, mark expected.
ExtensionContext.StorePer-namespace KV store with lifecycle-tied cleanup.
META-INF/services + junit.jupiter.extensions.autodetection.enabled=trueSPI-based global registration.
javascript
// 1. A custom extension — measure each test's wall clock
package com.example.test;

import org.junit.jupiter.api.extension.*;
import org.slf4j.*;

public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
    private static final Logger LOG = LoggerFactory.getLogger(TimingExtension.class);
    private static final ExtensionContext.Namespace NS =
        ExtensionContext.Namespace.create(TimingExtension.class);

    @Override
    public void beforeTestExecution(ExtensionContext ctx) {
        ctx.getStore(NS).put("t0", System.nanoTime());
    }

    @Override
    public void afterTestExecution(ExtensionContext ctx) {
        long t0 = (long) ctx.getStore(NS).remove("t0");
        long ms = (System.nanoTime() - t0) / 1_000_000;
        LOG.info("{} took {} ms", ctx.getDisplayName(), ms);
    }
}

// 2. Register it
@ExtendWith(TimingExtension.class)
class UserServiceTest { /* ... */ }

// 3. Or, register globally via SPI:
//    META-INF/services/org.junit.jupiter.api.extension.Extension
//    com.example.test.TimingExtension
//    + set junit.jupiter.extensions.autodetection.enabled=true

@TempDir · mock helpersBuilt-in helpers

@TempDir Path tmpAuto-managed temp directory per test or per class.
@TempDir(cleanup = CleanupMode.ON_SUCCESS)Keep the dir on failure — useful for debugging.
@ExtendWith(MockitoExtension.class)Mockito 3+ integration. Use @Mock / @InjectMocks fields.
@ExtendWith(SpringExtension.class) / @SpringBootTestSpring DI in tests — spins up an ApplicationContext.
@DataJpaTest / @WebMvcTest / @RestClientTestSlice tests — load just the layer you need.
@Testcontainers + @ContainerSpin up real DBs/queues per test class.
SystemStubs (system-stubs-jupiter)Stub env vars, stdout, system properties — isolation without globals.

Surefire · Failsafe · GradleBuild & CI

mvn test / gradle testRun unit tests (*Test.java by default).
mvn verify / gradle checkInclude integration tests (*IT.java via Failsafe).
mvn -Dtest='AT*#happy*' testFilter by class#method pattern.
mvn -Dgroups="smoke" -DexcludedGroups="slow" testTag include / exclude.
gradle test --tests "*.LoginTest.happy*"Filter (Gradle).
gradle test --info / --debugVerbose logging when something’s odd.
gradle test --rerun-tasks --tests "*Flaky*"Bypass up-to-date cache for a known-flaky class.
junit-platform.properties (src/test/resources)Engine-level config: parallelism, display-name strategy, defaults.
jacoco / jacocoTestReportCoverage — pair Jacoco with JUnit. Surefire integration is built-in.
--fail-fast / -DfailFast=trueStop after first failure.

Full test classEnd-to-end · UserService

Per-class instance, ordered lifecycle, plain @Test + @ParameterizedTest, assertAll grouping, assertThrows on the failure path, and a @Disabled with a ticket reference.

javascript
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DisplayName("UserService")
class UserServiceTest {

    UserRepo repo;
    UserService svc;

    @BeforeAll
    void initDb() { /* one-time DB seed — needs PER_CLASS to avoid static */ }

    @BeforeEach
    void newTransaction() {
        repo = new InMemoryUserRepo();
        svc  = new UserService(repo);
    }

    @Test @Order(1)
    @DisplayName("rejects blank email")
    void rejectsBlankEmail() {
        var ex = assertThrows(IllegalArgumentException.class,
                              () -> svc.register("", "pw"));
        assertTrue(ex.getMessage().contains("email"));
    }

    @ParameterizedTest(name = "{0} → {1}")
    @CsvSource({ "alice@x.com, alice", "bob@y.com, bob" })
    @Tag("smoke")
    void registersFromEmail(String email, String expectedHandle) {
        var u = svc.register(email, "pw");
        assertAll("user",
            () -> assertNotNull(u.id()),
            () -> assertEquals(expectedHandle, u.handle()),
            () -> assertEquals(email, u.email())
        );
    }

    @Test
    @Disabled("flaky on CI — TICK-742")
    void deletesAccount() { /* ... */ }

    @AfterEach
    void teardown() { repo.clear(); }
}

Best practiceGood to know

Use assertAll for related assertions. Without it, the first failure short-circuits the rest — you patch one field, rerun, see the next one fail, repeat. assertAll reports them all in one go.
Prefer @MethodSource over hand-rolled loops. One test method, N data rows, N report entries — the report shows which row failed. A for loop hides that.
Pin JUnit versions with the BOM. junit-jupiter, junit-platform-launcher, and Mockito’s JUnit integration can drift. The BOM keeps the graph consistent.

Common trapsWatch out for

Don’t mix JUnit 4 and 5 imports. org.junit.Test (JUnit 4) silently does nothing under Jupiter — tests appear green because they never run. Always import org.junit.jupiter.api.Test.
@BeforeAll needs to be static. … unless you add @TestInstance(PER_CLASS). Forgetting either yields a confusing "no enclosing instance" error.
Argument order matters: assertEquals(expected, actual). Reversed, the failure diff is technically right but reads backwards — "expected 'foo' but was 'bar'" with the real values swapped is a confusing 10 minutes.

Go deeperSee also

JUnit 5 FAQ

What is JUnit 5 and how is it different from JUnit 4?

JUnit 5 (Jupiter) is a complete rewrite of JUnit 4. It introduces a modular architecture (JUnit Platform + Jupiter + Vintage), richer extension model via @ExtendWith, parameterized tests with @ParameterizedTest, dynamic tests, and new assertion methods like assertAll and assertThrows. JUnit 4 annotations like @Test still exist but live in a different package.

What are the main JUnit 5 lifecycle annotations?

@Test marks a test method. @BeforeEach and @AfterEach run before and after each test. @BeforeAll and @AfterAll run once for the whole class (methods must be static unless @TestInstance(Lifecycle.PER_CLASS) is set). @Disabled skips a test with an optional reason message.

How do I write parameterized tests in JUnit 5?

Annotate the test with @ParameterizedTest and add a source annotation. @CsvSource({"1,1", "2,4"}) inlines values; @CsvFileSource reads a CSV; @MethodSource points to a factory method returning a Stream. The test method receives the values as parameters — types are auto-converted.

How do I use Mockito with JUnit 5?

Add mockito-junit-jupiter to your dependencies and annotate the test class with @ExtendWith(MockitoExtension.class). Declare fields with @Mock to create mocks and @InjectMocks to inject them into the class under test. Use when(mock.method()).thenReturn(value) to stub and verify(mock).method() to assert interactions.

What is the difference between @BeforeEach and @BeforeAll in JUnit 5?

@BeforeEach runs before every individual test method — use it for fresh setup that each test needs in isolation. @BeforeAll runs once before any test in the class — use it for expensive setup like starting a server or loading a large dataset. @BeforeAll methods must be static unless you use @TestInstance(Lifecycle.PER_CLASS).

How do I assert multiple conditions in JUnit 5?

Use assertAll() to group multiple assertions so all are evaluated even if some fail — unlike chained assertEquals calls that stop at the first failure. Example: assertAll("user", () -> assertEquals("Alice", u.name()), () -> assertTrue(u.active())). This gives you a complete failure report in one test run.