"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.jupiterjunit-jupiter5.11.0testmaven-surefire-plugin3.5.0maven-failsafe-plugin3.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
@Test
Marks a method as a test. Returns void. No public required.
@DisplayName("a friendlier name")
Custom name in reports + IDE.
@BeforeEach / @AfterEach
Run before / after each test. Reset state here.
@BeforeAll / @AfterAll
Run 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.
Coverage — pair Jacoco with JUnit. Surefire integration is built-in.
--fail-fast / -DfailFast=true
Stop 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.
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.
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.