Review and Stretch Goals

8 min read

You have built a test suite that spans every layer of JUnit 5: lifecycle annotations, the full assertion toolkit, parameterised tests from four different source types, extensions that inject resources and observe outcomes, conditional execution, parallel configuration, Selenium integration, and a Maven build that separates unit from integration tests and generates Allure reports. This lesson is for consolidating what you have built, assessing it honestly, and deciding where to push further.

Self-assessment checklist

Work through this list against your capstone project. Green means you understand the concept fully and applied it correctly. If something is yellow or red, go back to the relevant lesson and revisit the exercise.

Chapter 1 — Fundamentals

  • I understand why JUnit 5 ships as three separate modules and what each one is for.
  • My project has junit-jupiter with <scope>test</scope> and Surefire 3.2.5 — not the Maven-bundled version.
  • My test classes and methods have no public modifier — I am writing JUnit 5 style, not JUnit 4.
  • @BeforeAll and @AfterAll are static in my test classes (or I have used @TestInstance(PER_CLASS) deliberately).

Chapter 2 — Assertions & Organisation

  • I have used assertEquals with a delta for all double comparisons — no floating-point equality without tolerance.
  • I have used assertThrows for exception tests, and I assert the exception message, not just the type.
  • I have used assertAll in at least one test to validate multiple fields simultaneously.
  • My test classes use @Nested and @DisplayName so the report reads as a specification, not a list of method names.

Chapter 3 — Parameterised & Repeated Tests

  • Each calculator operation has at least five input/expected pairs tested with @CsvSource or @MethodSource.
  • The exception scenarios (divide by zero, negative sqrt) are tested with @MethodSource returning a Stream<Arguments>.
  • I have used @RepeatedTest on at least one method to verify consistency across multiple calls.
  • I understand when to use @ValueSource, @CsvSource, @CsvFileSource, and @MethodSource — and have used at least three.

Chapter 4 — Extensions

  • CalculatorTestExtension implements both BeforeTestExecutionCallback/AfterTestExecutionCallback and TestWatcher.
  • The extension is registered globally via META-INF/services — not with @ExtendWith on every class.
  • The timing output appears in the console for every test, including each @RepeatedTest repetition.

Chapter 5 — Selenium & Maven

  • Surefire is configured with <groups>unit</groups> so mvn test runs only unit tests.
  • mvn verify runs the full suite including Failsafe integration tests.
  • Allure shows a Behaviors view with feature groupings from @Feature annotations.
  • A GitHub Actions workflow runs on every push and uploads report artifacts with if: always().

Common patterns you should now recognise

After this course, the following patterns should be immediately readable when you encounter them in a codebase:

// Pattern 1: Extension-managed resource injection
@ExtendWith(WebDriverExtension.class)
class SomeTest {
    @Test void test(WebDriver driver) { ... }
}
 
// Pattern 2: Parameterised with external file
@ParameterizedTest
@CsvFileSource(resources = "/testdata/scenarios.csv", numLinesToSkip = 1)
void test(String input, int expected) { ... }
 
// Pattern 3: assertAll for multi-field validation
assertAll("response body",
    () -> assertEquals(200, response.getStatusCode()),
    () -> assertNotNull(response.jsonPath().getString("id")),
    () -> assertEquals("Alice", response.jsonPath().getString("name"))
);
 
// Pattern 4: Composed annotation
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
@Tag("smoke") @Test
public @interface SmokeTest { }

If any of these look unfamiliar, revisit the relevant lesson before the stretch goals.

Stretch goals

These extend the capstone significantly. Work through them in order — each depends on the previous.

Stretch 1 — REST API integration tests with RestAssured

Add rest-assured to the pom.xml (test scope). Start a WireMock or Spring Boot test server in a @BeforeAll (or a @RegisterExtension). Write CalculatorApiIT.java (the IT suffix triggers Failsafe, not Surefire) with tests for the POST /calculate endpoint. Use assertAll to validate status code, content type, and body together.

@Tag("integration")
class CalculatorApiIT {
 
    @Test
    void additionReturns200WithResult() {
        Response response = given()
            .contentType("application/json")
            .body("""{"operation":"add","a":10,"b":5}""")
            .post(System.getProperty("baseUrl") + "/calculate");
 
        assertAll("POST /calculate add",
            () -> assertEquals(200, response.getStatusCode()),
            () -> assertEquals("application/json", response.getContentType()),
            () -> assertEquals(15.0, response.jsonPath().getDouble("result"), 1e-9)
        );
    }
}

Run with mvn verify -Dgroups=integration -DbaseUrl=http://localhost:8080.

Stretch 2 — Selenium smoke tests

Add selenium-java and webdrivermanager to the pom.xml. Implement WebDriverExtension (from Chapter 4) and ScreenshotExtension. Write two or three Selenium tests that drive a real web application — the Sauce Labs demo app is public and suitable for practice. Tag them @Tag("selenium") and run with mvn test -Dgroups=selenium.

Stretch 3 — Parallel unit tests

Add parallel configuration to junit-platform.properties:

junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=4

Run the unit test suite and time it. Then add @Execution(ExecutionMode.SAME_THREAD) to the MultipleAssertions nested class (since it verifies all six operations in one call, ordering matters for diagnostics). Confirm the rest of the suite parallelises while that class remains sequential.

Stretch 4 — Data-driven from CSV file

Move the @CsvSource data for all operations into a single file src/test/resources/testdata/operations.csv. Use @CsvFileSource in each nested test class. Add a header row and set numLinesToSkip = 1. The test code should not contain any hardcoded values — all inputs and expected results come from the file. This mirrors the pattern teams use when a BA or product owner maintains test data independently of the test code.

Stretch 5 — Custom @CalculatorTest composed annotation

Reduce boilerplate by creating a composed annotation that bundles @Test, @Tag("unit"), and @Severity(SeverityLevel.NORMAL):

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("unit")
@Test
@Severity(SeverityLevel.NORMAL)
public @interface CalculatorTest { }

Replace @Test @Tag("unit") on every test method with @CalculatorTest. Add a @CriticalCalculatorTest variant that uses SeverityLevel.CRITICAL for divide-by-zero and sqrt-negative tests. Confirm Allure shows different severity colours for the two groups.

Where to go next

JUnit 5 is the foundation. The next layer depends on what you are testing:

REST API testing — combine JUnit 5 with RestAssured for request building and response parsing. Add WireMock for mocking third-party APIs in unit tests. The TestNG course's API chapters apply directly — RestAssured is framework-agnostic.

Spring Boot applications@SpringBootTest is a JUnit 5 extension that loads the full Spring context. @WebMvcTest loads only the web layer. If you are testing Spring applications, this is the natural next step after this course.

Mutation testing — PIT (Pitest) runs JUnit 5 tests against deliberately mutated versions of your source code to measure how well your tests actually catch bugs. Run mvn org.pitest:pitest-maven:mutationCoverage against the calculator project and read the mutation score.

Contract testing — Pact JVM integrates with JUnit 5. If your team builds microservices, contract tests verify that producer APIs match consumer expectations without end-to-end tests.

Performance testing — JMH (Java Microbenchmark Harness) uses JUnit-compatible annotations and Maven integration. If you need to measure throughput rather than correctness, JMH is the professional tool.

What made the difference in this course

The engineers who get the most out of JUnit 5 are the ones who understand the why behind each feature:

  • @ExtendWith over base-class inheritance — because composition beats inheritance for cross-cutting concerns.
  • assertAll over sequential assertions — because you should see all the failures at once, not one per CI cycle.
  • @MethodSource over @CsvSource for complex data — because objects carry more context than flat strings.
  • @ResourceLock in parallel suites — because parallelism without safety is just a faster way to get wrong results.
  • Surefire + Failsafe split — because unit tests should fail fast and integration tests should always produce a report.

Take those principles into every test suite you build, regardless of the framework.

Where to find the community

  • JUnit 5 User Guidejunit.org/junit5/docs/current/user-guide/ — the definitive reference. The sections on extensions and parameterised tests are especially detailed.
  • GitHub Discussionsgithub.com/junit-team/junit5/discussions — active community, responsive maintainers.
  • Stack Overflow [junit5] tag — thousands of answered questions covering edge cases.
  • Allure documentationdocs.qameta.io/allure/ — complete reference for annotations, plugins, and CI integration.

Learning paths from here

JUnit 5 Graduate
  • – RestAssured + JUnit 5
  • – WireMock for stubs
  • – Contract testing with Pact
  • – @SpringBootTest
  • – @WebMvcTest, @DataJpaTest
  • – Testcontainers for databases
  • – PIT mutation testing
  • – JaCoCo code coverage
  • – SonarQube quality gates
  • Allure CI plugins –
  • Test sharding across agents –
  • Flaky test quarantine –

Congratulations on completing the JUnit 5 course. You have covered every layer from a single @Test method to a parallel, parameterised, extension-driven suite with CI reporting. The capstone project is a real deliverable — show it.

// tip to track lessons you complete and pick up where you left off across devices.