Companion code for the IT Talk "JUnit 6 vs TestNG 7".
Previous edition: TestNG vs. JUnit 4 slides · TestNG vs. JUnit 4 webinar
Related projects:
- 🧪 TestNG Workshop — companion TestNG examples to compare side-by-side with this repo
- 🌐 Selenium Example (JUnit 6 branch) — real-world Selenium WebDriver framework built on top of JUnit 6
- Who Is This For?
- Prerequisites
- Quick Start
- Supported Versions
- Feature Map
- Learning Path — Beginners
- Advanced Topics — Path for Senior Engineers
- Command Examples
- Project Structure
- License
- Additional Resources
- Useful Links
| Audience | What you will get |
|---|---|
| QA engineers new to JUnit 6 | A guided tour of every major feature with runnable examples |
| Java developers migrating from JUnit 4 / TestNG | Side-by-side comparison of patterns and idioms |
| Senior / lead engineers | Deep-dives into extensions, retry strategies, parallel execution, and tagging |
| Workshop facilitators | A ready-made project you can hand to attendees |
| Tool | Minimum version | Notes |
|---|---|---|
| JDK | 21 LTS | |
| Maven | 3.9+ | |
| IDE | Any (IntelliJ IDEA recommended) | Lombok plugin required for IDE support |
| Lombok plugin | Latest | IntelliJ: Settings → Plugins → Lombok |
git clone https://github.com/a-oleynik/junit-workshop.git
cd junit-workshop
mvn clean test| Maven artifact | Version | Purpose |
|---|---|---|
junit-jupiter-engine |
6.1.0-M1 |
Test engine — discovers and runs Jupiter tests; transitively provides junit-jupiter-api (all @Test, @BeforeEach, @AfterAll, … annotations) |
junit-jupiter-params |
6.1.0-M1 |
@ParameterizedTest, @ValueSource, @CsvSource, @MethodSource, @CsvFileSource |
junit-platform-suite |
6.1.0-M1 |
@Suite, @SelectClasses, @BeforeSuite, @AfterSuite |
junit-pioneer |
2.3.0 |
@RetryingTest, @CartesianTest, and other community extensions |
junit-jupiter-params-dataprovider |
2.12 |
TNG-style @DataProvider integration for JUnit Jupiter |
assertj-core |
3.27.7 |
Fluent assertion library; SoftAssertions for collecting multiple failures |
hamcrest-library |
3.0 |
Matcher-based assertions — assertThat(value, matcher) |
lombok |
1.18.44 |
@Builder, @Data — compile-time code generation; reduces boilerplate in model classes |
rerunner-jupiter |
2.1.6 |
@RepeatedIfExceptionsTest — auto-retry flaky tests on failure |
opencsv |
5.12.0 |
CSV file parsing for data-driven tests (CSVParameterizationTest) |
| Java source / target | 21 |
Java language level for compilation |
| Package / folder | Feature demonstrated | Test class(es) |
|---|---|---|
general |
Basic assertions (assertEquals, assertTrue, assertNull, …) |
AssertTest |
general |
Exception testing (assertThrows) |
ExceptionTest |
general |
Test fixtures (@BeforeEach, @AfterEach, @BeforeAll, @AfterAll) |
FixturesTest |
general |
Hamcrest matchers | HamcrestTest |
general |
Timeouts (@Timeout) |
TimeoutTest |
general |
Disabling tests (@Disabled) |
DisabledTest |
general |
Display names & name generators | DisplayNameTest, DisplayNameGenerationTest |
group/asserts |
Grouped / soft assertions (assertAll) |
AssertAllTest |
group/asserts |
AssertJ soft assertions | SoftAssertionsAssertJTest, SoftAssertionsAssertJBDDTest |
group/asserts |
JUnit 5+ soft assert pattern | SoftAssertTest |
conditional |
Assumptions (assumeTrue, assumeThat) |
AssumptionsTest, AssumptionsBeforeAllTest |
ddt |
Parameterized tests — @MethodSource |
ParameterizationTest |
ddt |
Parameterized tests — @ValueSource / @CsvSource |
ValueSourceTest |
ddt |
CSV file data source | CSVParameterizationTest |
ddt |
TNG DataProvider integration | DataProviderTest |
ddt |
JUnit Pioneer Cartesian product | PioneerCartesianProductTest |
nested |
@Nested test classes |
NestedTest |
grouping |
Tagging with @Tag and custom tag annotations |
TagsTest |
execution/order |
Test execution ordering (@TestMethodOrder) |
ExecutionOrderWithTest |
extensions |
Custom Extension (@RegisterExtension) |
DBResourceExtensionTest |
extensions |
TestWatcher extension |
TestWatcherExtensionTest |
retry |
Retry with JUnit Pioneer (@RetryingTest) |
RetryPioneerTest |
retry |
Retry with Rerunner Jupiter | RetryRerunnerTest |
repeat |
@RepeatedTest |
RetryRepeatedTest |
suite |
Suite lifecycle (@Suite, @BeforeSuite, @AfterSuite, @SelectClasses) |
BeforeAfterDemoSuite, SuiteLifecycleFirstCase, SuiteLifecycleSecondCase |
suite/extension |
Suite-like global lifecycle via BeforeAllCallback + root ExtensionContext store |
SuiteExtensionFirstTest, SuiteExtensionSecondTest |
Work through these topics in order; each builds on the previous one.
-
Basic assertions →
AssertTest
LearnassertEquals,assertTrue,assertNull,assertAll, andfail. -
Test lifecycle →
FixturesTest
Understand@BeforeEach,@AfterEach,@BeforeAll,@AfterAll. -
Exception testing →
ExceptionTest
UseassertThrowsto assert that code throws the right exception. -
Disabling & display names →
DisabledTest,DisplayNameTest
Skip tests cleanly and make reports human-readable. -
Hamcrest matchers →
HamcrestTest
Write expressive assertions withassertThat. -
Grouped assertions →
AssertAllTest
UseassertAllso multiple failures are reported together. -
Assumptions →
AssumptionsTest
Skip tests dynamically when preconditions aren't met. -
Parameterized tests →
ValueSourceTest,ParameterizationTest
Drive one test method with many data rows. -
Nested tests →
NestedTest
Organise related scenarios using inner@Nestedclasses. -
Tagging →
TagsTest
Mark tests asSmokeorRegressionand run subsets from the command line.
Run the whole beginner suite:
mvn clean testThese topics assume familiarity with JUnit 5+ basics.
TagsTest → tags/ package
Compose @Tag into reusable meta-annotations (@Smoke, @Regression).
CSVParameterizationTest, DataProviderTest, ParameterizationTest
Load test data from CSV files and external ArgumentsProvider / @MethodSource classes.
PioneerCartesianProductTest
Generate all combinations of parameter sets automatically.
SoftAssertionsAssertJTest, SoftAssertionsAssertJBDDTest
Collect all assertion failures before reporting — no early bail-out.
DBResourceExtensionTest, TestWatcherExtensionTest
Implement BeforeAllCallback, AfterAllCallback, and TestWatcher to manage external resources and observe test
outcomes.
ExecutionOrderWithTest
Control method execution order with @TestMethodOrder and @Order.
RetryPioneerTest — @RetryingTest(maxAttempts, minSuccess) via JUnit Pioneer
RetryRerunnerTest — Rerunner Jupiter integration
RetryRepeatedTest — JUnit 5+ native @RepeatedTest
Configured globally in pom.xml via Surefire:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
Tests run concurrently by default. Use @ResourceLock or @Execution(SAME_THREAD) to serialise where needed.
# Run only Smoke tests
mvn clean test -P SmokeTests
# Run only Regression tests
mvn clean test -P RegressionTestsBeforeAfterDemoSuite → suite/ package
Group multiple test classes under a single @Suite class.
Use @BeforeSuite and @AfterSuite to run setup/teardown logic that wraps the entire suite — not just a single
test class.
@Suite
@SelectClasses({
SuiteLifecycleFirstCase.class,
SuiteLifecycleSecondCase.class
})
public class BeforeAfterDemoSuite {
@BeforeSuite
static void beforeSuite() { ...}
@AfterSuite
static void afterSuite() { ...}
}
⚠️ Naming convention: classes selected by a suite must not be named*Testor*Tests.
Use*Caseor*Scenarioinstead.
If they matched Surefire’s default discovery patterns they would execute twice —
once directly by Surefire and once again through the suite.
⚙️ Maven config:
pom.xmlrequires two things for the suite feature to work:
- The
junit-platform-suitedependency (enables@Suite,@BeforeSuite,@AfterSuite):<dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-suite</artifactId> <version>6.1.0-M1</version> </dependency>**/*Suite.javaadded to Surefire<includes>soBeforeAfterDemoSuiteis automatically discovered bymvn clean test.
SuiteLikeLifecycleExtension → extensions/ package (main); tests → suite/extension/
An alternative to @Suite that provides global setup/teardown without grouping tests under a suite class.
How it works:
- Implement
BeforeAllCallback - Access the root
ExtensionContext— shared across the entire JVM test run getOrComputeIfAbsentensures the factory runs only once, on the first class whosebeforeAllfires- Return an
AutoCloseable— JUnit callsclose()when the root context tears down (end of run = “AfterSuite”)
public class SuiteLikeLifecycleExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
context.getRoot()
.getStore(NAMESPACE)
.getOrComputeIfAbsent("suite-like-resource", key -> {
System.out.println("Before entire run"); // runs only once
return new SuiteCleanupResource();
}, SuiteCleanupResource.class);
}
static class SuiteCleanupResource implements AutoCloseable {
@Override
public void close() {
System.out.println("After entire run"); // called once at end of all tests
}
}
}Apply @ExtendWith to every test class that participates in the shared lifecycle:
@ExtendWith(SuiteLikeLifecycleExtension.class)
public class SuiteExtensionFirstTest {
@Test
void first() { ...}
}Comparing the two suite-lifecycle approaches:
@Suite + @BeforeSuite |
Extension approach | |
|---|---|---|
| Test class naming | *Case / *Scenario (not *Test) |
*Test — normal, runs independently |
| Requires suite class | ✅ @SelectClasses required |
❌ No suite class needed |
| Tests run independently | ❌ Only via suite entry class | ✅ Normal Surefire discovery |
| Opt-in mechanism | Declared in @SelectClasses |
@ExtendWith per class |
⚠️ Note ongetOrComputeIfAbsent: the 3-argument overload used here is deprecated in JUnit 6 but is the standard API in JUnit 5. This example targets the JUnit 5 branch.
mvn clean site
# or
mvn clean surefire-report:reportmvn clean testmvn clean test -Dtest=AssertTestmvn clean test -Dtest=AssertTest#assert_equals_multiplication_testmvn clean test -Dtest=AssertTest,HamcrestTestmvn clean test -Dtest=AssertTest#assert_equals*mvn clean test -Dtest=AssertTest#assert_equals*+assert_boolean*mvn clean test -Dsurefire.rerunFailingTestsCount=2mvn clean test -Dgroups=Regression,Smokemvn test "-Dtest=BeforeAfterDemoSuite"
⚠️ Naming convention: suite-member classes (e.g.SuiteLifecycleFirstCase) are named*Case, not*Testor*Tests.
This prevents Surefire from discovering them as standalone tests and running them twice —
once directly by Surefire and once again through the suite.
mvn clean installmvn clean install -DskipTestsmvn clean surefire-report:reportmvn clean siteReports are written to
target/site/surefire-report.html
mvn clean test -Xsrc/
├── main/java/com/oleynik/qa/workshop/junit/
│ ├── extensions/ # Extension implementations (SuiteLikeLifecycleExtension, DBResourceExtension, TestWatcherExtension)
│ └── model/ # Domain model (User, MyDoubleWrapper, MyServer)
└── test/java/com/oleynik/qa/workshop/junit/
├── general/ # Core assertions, fixtures, exceptions, display names
├── group/asserts/ # Grouped / soft assertions
├── conditional/ # Assumptions
├── ddt/ # Parameterized & data-driven tests
├── nested/ # @Nested test classes
├── grouping/ # @Tag / custom tag annotations
├── execution/order/ # Test execution ordering
├── extensions/ # Custom JUnit 5+ extensions
├── retry/ # Retry strategies (Pioneer, Rerunner)
├── repeat/ # @RepeatedTest
└── suite/ # Suite lifecycle (@BeforeSuite, @AfterSuite)
└── extension/ # Suite-like global lifecycle via BeforeAllCallback
This project is licensed under the MIT License — see the LICENSE file for details.
- JUnit 6 User Guide
- JUnit Pioneer Documentation
- AssertJ Documentation
- Hamcrest Tutorial
- Lombok Features
- Maven Surefire Plugin
- Maven Surefire Report Plugin
- TestNG Workshop — companion TestNG examples
- Selenium Example — JUnit 6 branch — real-world Selenium framework using JUnit 6
- Java Download: https://www.oracle.com/java/technologies/downloads/
- Maven Download: https://maven.apache.org/download.cgi
- JUnit 6 Releases: https://github.com/junit-team/junit5/releases
- JUnit Pioneer Releases: https://github.com/junit-pioneer/junit-pioneer/releases
- Lombok Download: https://projectlombok.org/download
- IntelliJ Lombok Plugin: https://plugins.jetbrains.com/plugin/6317-lombok