The previous five chapters built every component of a Karate framework one lesson at a time: project setup, HTTP verbs, assertions, schema validation, variables, file reading, data-driven testing, feature reuse, authentication patterns, retry polling, parallel execution, and CI/CD. The capstone puts them together against a realistic end-to-end scenario — a user management API called TeamHub. You'll build a complete Karate suite, from pom.xml to GitHub Actions, covering CRUD, roles, teams, data-driven cases, schema validation, and parallel execution. By the end you'll have a portfolio-quality project that demonstrates every skill the course covered.
The scenario: TeamHub
TeamHub is a fictional SaaS platform for managing engineering teams. Three roles, three resources, and a business workflow around team membership:
- Roles —
admin(full access to everything),manager(manage their team's members and permissions),member(view team data, update own profile). - Resources — users (name, email, role, status), teams (name, description, ownerId), memberships (userId, teamId, joinedAt).
- Workflow — an admin creates a team, adds users as members, a manager updates a member's role, a member views their team — and all of this is protected by the role that the Bearer token carries.
That's a small, plausible API. It exercises every Karate feature without becoming a project that takes a week to build.
The endpoints
Imagine the API looks like this:
Auth
POST /auth/login — returns Bearer token
POST /auth/logout — invalidates token
Users
GET /users — list (admin/manager)
POST /users — create user (admin)
GET /users/{id} — get user (admin/manager/self)
PUT /users/{id} — full update (admin/manager)
PATCH /users/{id} — partial update (admin/manager/self)
DELETE /users/{id} — delete (admin)
Teams
GET /teams — list (admin/manager)
POST /teams — create (admin)
GET /teams/{id} — get team (admin/manager/member)
PUT /teams/{id} — update (admin/manager)
DELETE /teams/{id} — delete (admin)
Memberships
POST /teams/{id}/members — add member (admin/manager)
DELETE /teams/{id}/members/{userId} — remove member (admin/manager)
GET /teams/{id}/members — list members (admin/manager/member)
There is no live TeamHub server — that's intentional. Stub it with Mockoon, WireMock standalone, or json-server. Alternatively, substitute JSONPlaceholder for the happy-path tests and focus on the framework structure. What matters is what you build, not which server it talks to.
Deliverables
By the end of the project you should be able to point at a Git repository and run mvn clean verify to see everything work end to end.
1. Maven project (pom.xml)
One dependency: karate-junit5 at test scope. Java 21 toolchain. The <testResources> block so Maven picks up .feature files from src/test/java.
2. karate-config.js with environment switching
Returns baseUrl that switches between dev and staging based on karate.env. Reads credentials from java.lang.System.getenv(). Sets global auth headers via karate.configure('headers', ...) using karate.callSingle() to log in once per suite.
3. Reusable common features
common/login.feature— acceptsemailandpassword, returnsauthToken.common/create-user.feature— accepts user fields, POSTs to/users, returnsuserId.common/create-team.feature— accepts team fields, POSTs to/teams, returnsteamId.common/helpers.js—generateEmail(prefix),generateName()usingjava.lang.System.currentTimeMillis().
4. At least 20 scenarios across 5 feature files
| Feature file | Scenarios | What it covers |
|---|---|---|
auth/auth.feature | 4 | Login success, wrong password → 401, missing token → 401, expired token → 401 |
users/users.feature | 6 | CRUD, list pagination, GET own profile, schema validation |
teams/teams.feature | 4 | Create, read, update, delete — with role-based access checks |
memberships/memberships.feature | 4 | Add member, list members, remove member, member views their team |
users/users-data-driven.feature | 3+ | Scenario Outline from CSV for user creation validation cases |
5. Data-driven tests with Scenario Outline + CSV
users/test-users.csv with valid and invalid user rows. A Scenario Outline that runs each row and asserts the correct status code — 201 for valid, 400/422 for invalid.
6. Schema validation with Karate markers on all responses
schemas/user-schema.json and schemas/team-schema.json using fuzzy markers. Every GET scenario validates the response shape with match response == read('classpath:schemas/user-schema.json').
7. Retry pattern for at least one async operation
One scenario that simulates an async operation (e.g., user provisioning) with configure retry and retry until response.status == 'active'. Even a 3-attempt retry against a synchronous stub demonstrates the pattern.
8. Parallel execution runner
ParallelRunner.java using Runner.path("classpath:").outputCucumberJson(true).parallel(4). The assertEquals guard fails the Maven build on any scenario failure.
9. GitHub Actions CI pipeline
.github/workflows/karate-tests.yml that runs on push/PR, injects environment secrets, runs mvn clean verify, uploads the Karate report with if: always().
10. Reporting
Built-in Karate HTML report plus maven-cucumber-reporting plugin generating target/cucumber-html-reports/.
Stretch goals:
- Karate UI test that logs into a frontend and verifies a user appears after API creation
- Allure integration with trend tracking across runs
common/wait-for-status.featureas a reusable polling helper- Gatling simulation reusing
users.featurefor a basic load test
How the deliverables map to the course chapters
- – Maven project + runner
- – karate-config.js
- – CRUD feature files
- – match assertions + schema
- – def + embedded expressions
- – read() for JSON/CSV files
- – Scenario Outline + CSV
- – call / callonce login
- – Global auth in karate-config.js
- – retry until for async
- – parallel(4) runner
- – Karate UI smoke check
- Built-in HTML report –
- outputCucumberJson(true) –
- maven-cucumber-reporting –
- GitHub Actions + artefacts –
Suggested project structure
teamhub-karate/
├── pom.xml
├── .github/workflows/karate-tests.yml
└── src/test/java/
├── karate-config.js
├── ParallelRunner.java
├── common/
│ ├── login.feature
│ ├── create-user.feature
│ ├── create-team.feature
│ └── helpers.js
├── schemas/
│ ├── user-schema.json
│ └── team-schema.json
├── auth/
│ └── auth.feature
├── users/
│ ├── users.feature
│ ├── users-data-driven.feature
│ └── test-users.csv
├── teams/
│ └── teams.feature
└── memberships/
└── memberships.feature
Eight directories, each with a single concern. The runner at the root finds everything via classpath:.
Suggested order of work
A reasonable sequence over 5–8 sessions:
- Scaffold the Maven project and
karate-config.js. Point at JSONPlaceholder or your stub. Write one trivial GET scenario that asserts 200. Commit and confirm CI runs it. - Authentication. Build
common/login.feature. Wirecallonceinto at least one feature file's Background. Write the four auth scenarios (valid login, wrong password, missing token, expired token). - Users CRUD. Write all six user scenarios using
call common/create-user.featurefor setup. Add schema validation for GET responses. - Teams and memberships. Repeat the CRUD pattern. Write the add-member/remove-member/list-members scenarios. Assert the membership list changes correctly.
- Data-driven. Create
test-users.csvand write the Scenario Outline. Include at least three valid rows and two invalid rows. - Parallel runner and CI. Replace the per-feature runners with
ParallelRunner.java. Add the GitHub Actions workflow. Push and confirm the CI job passes. - Reporting. Add
.outputCucumberJson(true)and the Masterthought plugin. Runmvn verify. Share the report link. - Polish and stretch. Add retry to one scenario, add a Karate UI smoke check, or wire Allure.
Each step is one focused session. The full project is a comfortable week of evening work.
⚠️ Common mistakes
- Building all the helpers before the first test runs. Scaffold everything up front and you'll spend hours writing code that doesn't work yet. Instead: write one test, make it green, extract the helper only when a second test needs the same logic. The capstone framework should emerge from the tests, not precede them.
- No cleanup between tests. Scenarios that create users without deleting them pollute the stub or database by hundreds of rows across multiple runs. Write teardown calls (
method delete) at the end of creation scenarios, or usekarate.callSingle()to create shared test data once and reuse it instead of creating fresh data per scenario. - Skipping the negative test cases. The most realistic part of this capstone is the auth matrix: does a
membertoken correctly get a 403 on an admin-only endpoint? Does a missing token get a 401? These cases take five minutes to write and are the most commonly skipped in real projects. Write them first.
🎯 Practice task
This is the project. Your practice is to build it across 5–8 focused sessions.
- Today (45 min): scaffold the Maven project. Add
karate-junit5, the<testResources>block, and a minimalkarate-config.jspointing at JSONPlaceholder or your stub. Write one GET scenario. Run it green. Push to a new GitHub repository. - Session 2 (90 min): build
common/login.feature(real or simulated) and wirecallonceintoauth/auth.feature. Write the four auth scenarios. - Session 3 (90 min): write all six user scenarios. Add
schemas/user-schema.jsonand validate GET responses withmatch response == read(...). - Session 4 (90 min): write teams and memberships. Use
call common/create-team.featurefor setup data. Write at least one scenario per membership operation. - Session 5 (60 min): write the Scenario Outline with CSV. Add
ParallelRunner.java. Add the GitHub Actions workflow. - Session 6 (45 min): add
outputCucumberJson(true), the Masterthought plugin, andmvn verify. Confirm both report types generate. - Session 7 (optional stretch): add retry to one scenario, add a Karate UI smoke check, or integrate Allure.
The next lesson is the guided walkthrough — read it when you're stuck on a specific component, or after you've finished as a sanity check against the reference implementation.