Review and Stretch Goals

8 min read

You've built the BookVault framework. The 30 tests pass green, the CI pipeline runs on every push, the Allure report is bookmarked. This last lesson is the structured wind-down: a checklist to hold your project against, a few reflection prompts that turn knowledge into intuition, five stretch goals to take you beyond what the course covered, and the map of where to go next. The course ends here; the practice doesn't.

Self-assessment checklist

Walk through this list with your project open. Tick what's true; the gaps are your priorities for the next refactor.

Project setup

  • Maven pom.xml uses <properties> for every version.
  • Surefire is wired to a configurable suiteXmlFile.
  • At least two suite files exist (smoke + full).
  • .gitignore excludes target/, IDE files, and any local credential file.

Models

  • Every model uses Lombok (@Data @Builder minimum).
  • Response models have @JsonIgnoreProperties(ignoreUnknown = true).
  • Partial-update requests use @JsonInclude(NON_NULL).
  • Generic ApiResponse<T> exists where the API uses an envelope.
  • An ErrorResponse model is used by at least one negative test.

Auth

  • TokenManager caches per-role tokens, refreshes before expiry.
  • Credentials read from Config (env vars), never hardcoded.
  • At least one test asserts a token-expiry rejection.
  • An authorisation matrix test covers admin / librarian / member / guest × at least three endpoints.

Specs and helpers

  • One Specs per role, plus a guest spec.
  • ResponseSpecs for OK / CREATED / NOT_FOUND / UNAUTHORIZED / FORBIDDEN.
  • One helper per resource (BookApiHelper, MemberApiHelper, BorrowingApiHelper).
  • Helpers happy-path only; failure tests inline the call.

Tests

  • At least 30 tests across the categories named in the brief.
  • Each test class extends BaseApiTest.
  • At least one @AfterMethod(alwaysRun = true) cleanup hook.
  • At least one @DataProvider driven test.
  • At least two JSON Schema validations.
  • Each test reads as business intent; no JsonPath strings in the test method body for non-trivial cases.

Logging and reporting

  • enableLoggingOfRequestAndResponseIfValidationFails() set globally.
  • LogConfig.blacklistHeader("Authorization") configured.
  • Allure (or alternative) wired in; helpers annotated with @Step where it adds clarity.
  • Failed CI runs produce a downloadable, readable artefact.

CI/CD

  • GitHub Actions workflow exists.
  • Triggers: push (main), pull_request, schedule (nightly).
  • Secrets injected via ${{ secrets.* }}.
  • Surefire and Allure artefacts uploaded with if: always().
  • Allure report published to GitHub Pages (or equivalent stable URL).
  • Failure notification (Slack / email) on red main builds.

If you ticked 25+ of those, your project is portfolio-quality. If you ticked 35+, you've over-engineered (and that's fine — it's a learning project).

Reflection questions

Spend ten minutes writing answers (in a journal, README, or just out loud). The act of articulating answers is what consolidates the chapter-by-chapter learning into permanent intuition.

  • Where did you spend the most time? Was it the framework plumbing, the test writing, or the CI wiring? Where you spent time tells you where your weakest skill lay coming in.
  • What part of the framework is the most over-engineered? Be honest. Capstone projects have natural over-engineering — the question is which parts to remove on the next greenfield project.
  • What test would you find easiest to break? Imagine the API team renames copiesAvailable to availableCopies. Trace what fails. If everything fails (or nothing fails), the test layout is too coupled — or the schema test isn't doing its job.
  • What would you change about the BookVault domain? The brief is intentionally simple. If you've used a real API, what real-world concerns surfaced (idempotency, eventual consistency, versioned APIs) that this scenario didn't cover?
  • What part of Rest Assured are you still uncomfortable with? That's where the next 10 hours of practice should go.

These questions don't have right answers. They have useful answers — the ones that turn into your next project's architectural decisions.

Stretch goal #1 — Allure with custom steps and attachments

@Step on helpers is the floor. Custom step names with parameter substitution and per-step attachments are the ceiling:

@Step("Borrow book {bookId} as member {memberId}")
public static Borrowing borrow(int memberId, int bookId) {
    BorrowRequest req = new BorrowRequest(memberId, bookId);
    Allure.addAttachment("Request", "application/json",
        new ObjectMapper().writeValueAsString(req));
 
    Response response = given().spec(Specs.MEMBER)
        .body(req)
    .when().post("/borrowings");
 
    Allure.addAttachment("Response", "application/json", response.asString());
 
    return response.then()
        .spec(ResponseSpecs.CREATED)
        .extract().as(Borrowing.class);
}

The report now shows each helper as a named step with its inputs and the API's full reply attached as collapsible JSON. Reading a failure becomes scrolling through the failed test and clicking the failing step — no need to download artefacts.

Stretch goal #2 — parallel execution with thread safety

testng.xml with parallel="methods" thread-count="8" halves your suite time. Two things must hold for it to be safe:

  • TokenManager is already thread-safeConcurrentHashMap plus the synchronized block on the cache miss. ✓
  • Test data is unique per call — factories with UUID suffixes guarantee no row-level collisions. ✓
  • No shared static mutable state in tests. This is the trap. A static int counter that two test methods increment to build emails will produce duplicates under contention. Either keep counters out of test classes or use AtomicInteger.

Audit your project for static non-final fields. Each one is a parallel-execution liability.

Stretch goal #3 — contract testing via response → schema generation

Instead of writing a JSON Schema by hand, generate it from a known-good response and check every subsequent run against it:

public static void captureSchemaFromResponse(String resourcePath, Response response) {
    JsonSchemaInferrer inferrer = JsonSchemaInferrer.newBuilder()
        .setSpecVersion(SpecVersion.DRAFT_07)
        .build();
    JsonNode body = new ObjectMapper().readTree(response.asString());
    JsonNode schema = inferrer.inferForSample(body);
    Files.writeString(Paths.get("src/test/resources/schemas/" + resourcePath),
        schema.toPrettyString());
}

Run this once against a trusted response; commit the generated schema. Every subsequent run validates against it. Drift becomes immediately visible — and the schema is genuine to the API rather than to your understanding of it.

Stretch goal #4 — response-time tracking and SLA assertions

Soft perf signals belong in the suite. Add a TimingFilter that records every call, and a final report that flags regressions:

public class TimingFilter implements Filter {
    private static final Map<String, List<Long>> times = new ConcurrentHashMap<>();
 
    @Override
    public Response filter(FilterableRequestSpecification req,
                           FilterableResponseSpecification res,
                           FilterContext ctx) {
        long start = System.currentTimeMillis();
        Response response = ctx.next(req, res);
        long elapsed = System.currentTimeMillis() - start;
 
        String key = req.getMethod() + " " + req.getURI()
            .replaceAll("/\\d+", "/{id}");   // collapse path IDs
        times.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(elapsed);
        return response;
    }
 
    public static void printSummary() {
        times.forEach((endpoint, list) -> {
            long max = list.stream().mapToLong(Long::longValue).max().orElse(0);
            long avg = list.stream().mapToLong(Long::longValue).sum() / list.size();
            System.out.printf("%s — n=%d avg=%dms max=%dms%n",
                endpoint, list.size(), avg, max);
        });
    }
}

Wire TimingFilter.printSummary() into @AfterSuite. Push the output as an artefact. After a few runs, you have a per-endpoint baseline — and a regression in any endpoint's typical response time becomes a Git-blame-able event. Real SLA testing is the realm of K6 / JMeter (a separate course); this is the lightweight version that lives in your functional suite.

Stretch goal #5 — UI-API integration via Selenium

The most powerful stretch: combine the BookVault API tests with a Selenium UI suite. The pattern:

  1. API creates the dataBookApiHelper.createBook(BookFactory.random()) returns a typed Book.
  2. Selenium drives the UI — navigates to the catalogue page, searches for the book by title, asserts it appears.
  3. Assertions span layers — UI shows the title, copies-available counter; API confirms the underlying state.

The framework patterns transfer: BaseTest, Specs, Helpers work the same way for UI tests using Selenium WebDriver. The win is speed: setup that would take 30 seconds in the UI takes 200 ms via the API. The Selenium with Java course teaches the UI side; combining it with this course's API framework is a serious portfolio project.

Where to go next

Rest Assured complete
  • – API Automation with Karate — BDD-flavoured, no-Java
  • – Selenium with Java — combine UI + API tests
  • – Microservices Testing — contract + e2e + chaos
  • – K6 — JS-based, dev-friendly load tests
  • – JMeter — GUI-driven, enterprise-classic
  • – Gatling — Scala-based, high-throughput
  • – GraphQL testing — query / mutation / subscription
  • – gRPC testing — protobuf-based RPC
  • – WebSocket testing — bidirectional, stateful
  • Contract testing with Pact (consumer-driven) –
  • Mutation testing with PIT –
  • Chaos engineering with Toxiproxy / Litmus –
  • API Testing Masterclass — design patterns and strategy –
  • Core Java for QA — language fluency –
  • TypeScript for QA — alternative ecosystem –

The branches aren't sequential — they're parallel. A backend QA might pick K6 next; a full-stack QA might pick Selenium with Java; a security-leaning QA might pick contract testing or chaos engineering. The unifying skill is the one this course taught: turning a domain into typed, automated, observable tests.

Three habits that keep your skill alive

The biggest risk after a course is forgetting it. Three habits that prevent that:

  • Maintain one personal sandbox repo. Pick a public API (PokeAPI, OpenWeatherMap, GitHub's API, your own staging) and keep a Rest Assured project running against it. When you encounter a new technique at work, prototype it in the sandbox first. The repo becomes your portfolio and your scratch pad.
  • Read other people's tests. Open-source Java test suites (Spring's, Hibernate's, Apache projects') are master classes in real test design. One hour a fortnight reading is more education than another month of writing.
  • Teach the next person. The fastest way to consolidate this material is to walk a colleague through it. The questions they ask are the gaps in your own model. Volunteer to onboard the next QA hire.

The course in one sentence

If you remember nothing else: turn the API contract into typed Java models, build the smallest framework that makes test methods read as business intent, and put the suite in CI before it has more than ten tests. Everything in this course is in service of those three principles — and once you've felt the win on one project, you'll never want to work without them.

You're done. Take the project, push it to GitHub, link it from your CV, and move on to the next thing on the map. Good luck.

⚠️ Common mistakes (the long view)

  • Treating the capstone as the destination. It's the floor — the framework you've built should be the minimum you'd accept on day one of a new project. Real-world projects accumulate complexity (versioned APIs, multi-tenant auth, eventual consistency) that this scenario didn't ask for. Stay open to relearning.
  • Letting the framework calcify. The patterns in this course are the 2026 best practice; the 2028 best practice will be different. Read release notes for Rest Assured, Jackson, TestNG. Try alternatives (Karate, REST-assured-kotlin, Java records as POJOs) so the patterns you keep are kept deliberately, not by inertia.
  • Optimising the framework instead of writing tests. A polished framework with five tests is a hobby. A workmanlike framework with a hundred tests catches bugs. Always optimise for the test count metric first; the framework follows.

🎯 Practice task — finishing moves

A short, deliberate end. 60–90 minutes total.

  1. Run the assessment checklist above on your project. Tick every line you can; list every line you can't. The list is your next sprint.
  2. Pick one stretch goal. Implement it. Don't pick the easiest — pick the one that addresses your weakest area (parallel execution if you avoided it; Allure if your reports are bare).
  3. Push the project to a public GitHub repo. Add a clear README: what BookVault is, how to run the suite, where the report lives. The README is the project's front door.
  4. Link the repo from your CV / LinkedIn. A capstone project that nobody can see doesn't help your career.
  5. Write a 200-word retro. What you learned, what surprised you, what you'd do differently. Save it. Re-read it before your next API automation project.
  6. Pick the next course on the concept map. Schedule the first session.
  7. Schedule a calendar reminder for three months out to revisit the project — refactor what's now obvious, replace what's now outdated.
  8. Stretch: review the project with another engineer (a teammate, a mentor, an LLM). Ask three questions: What would you delete? What would you add? What would you rename? Their answers are gifts.

That's the course. Thanks for working through it. Now go and write the tests that make your team trust their deployments.

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