Karate earns its popularity: API tests as Gherkin-flavored feature files with a real JS engine underneath, one standalone jar with no Maven project required, and β rare among test frameworks β its own built-in mock server. It's actively maintained (2.1.2 shipped in August 2026). We're not an alternative to it; Karate is the client side of the test. This guide is about the other half: every Karate suite needs a server to hit. Below is a complete feature file that passes verbatim with zero setup, the ephemeral-backend pattern where the suite creates and destroys its own seeded API, deterministic retry and timeout drills, and an honest, hands-on look at karate mock β including exactly where it's the right tool instead of us.
Everything on this page was run with the Karate 2.1.2 standalone jar against production before publishing; the outputs and error messages shown are real.
# grab the standalone jar (no Maven project needed)
curl -sL -o karate.jar https://github.com/karatelabs/karate/releases/download/v2.1.2/karate-2.1.2.jar
# save the feature below as demo.feature, then:
java -jar karate.jar run demo.feature
Feature: demo products happy path
Background:
* url 'https://mockbird.mockbird.workers.dev'
Scenario: create a product, read it back, clean up
Given path 'm', 'demo', 'products'
And request { name: 'karate widget', price: 9.99 }
When method post
Then status 201
* def newId = response.id
Given path 'm', 'demo', 'products', newId
When method get
Then status 200
And match response.name == 'karate widget'
Given path 'm', 'demo', 'products', newId
When method delete
Then status 200
No token, no signup: /m/demo is a public playground that reseeds daily. The POST is a real write β the read-back proves it persisted. That's the difference between this and the classic tutorial targets: JSONPlaceholder returns {"id": 101} and stores nothing, so a Karate suite chaining a create into a read-back fails against it.
The demo project is shared, so for real suites make an isolated backend per run. Karate's save-and-reuse flow (def from a response) makes the full lifecycle one scenario β no hooks, no conftest, no setup script:
Feature: Mockbird sandbox lifecycle from a Karate suite
Background:
* url 'https://mockbird.mockbird.workers.dev'
Scenario: create project, CRUD, simulate failures, clean up
# 1. create a disposable seeded backend (30 products, orders, customers)
Given path 'api', 'projects'
And request { preset: 'ecommerce' }
When method post
Then status 201
* def projectId = response.id
* def adminKey = response.adminKey
# 2. read seeded data with query params
Given path 'm', projectId, 'products'
And param limit = 3
And param sortBy = 'price'
And param order = 'desc'
When method get
Then status 200
And assert response.length == 3
And assert response[0].price >= response[1].price
# 3. write persists
Given path 'm', projectId, 'products'
And request { name: 'karate widget', price: 9.99 }
When method post
Then status 201
* def newId = response.id
Given path 'm', projectId, 'products', newId
When method get
Then status 200
And match response.name == 'karate widget'
# 4. delete the project β everything gone
Given path 'api', 'projects', projectId
And header x-admin-key = adminKey
When method delete
Then status 200
Given path 'm', projectId, 'products'
When method get
Then status 404
This exact file ran green in 2.7 seconds against production. The create response is flat β { id, adminKey, ... } β so the two def lines are all the bookkeeping there is. Anonymous projects are capped at 10 per IP per rolling 24h; deleting doesn't refund the slot, so CI runners doing many runs per day should create a free account and pass its key instead.
retry until meets mock_seqKarate has first-class retry syntax. What it can't conjure is a server that fails predictably. ?mock_seq=503,503,200 makes the endpoint answer 503, 503, then 200 β in that order, across requests β so the retry path executes deterministically every run:
Scenario: retry until the flaky endpoint recovers (deterministic)
* configure retry = { count: 5, interval: 500 }
Given path 'm', 'demo', 'products'
And param mock_seq = '503,503,200'
And param limit = 1
And retry until responseStatus == 200
When method get
Then status 200
Each retry attempt is a new request, so it advances the sequence: attempt 1 β 503, attempt 2 β 503, attempt 3 β 200. Drop count to 2 and the scenario fails β which is exactly the assertion you want when you're testing that your client gives up correctly. One trap from our Tavern write-up applies here too: adding mock_seq_reset=1 to a retried request restarts the sequence on every attempt, so the retry loop never recovers. Use plain mock_seq for retry tests.
configure readTimeoutScenario: slow endpoint inside the budget
* configure readTimeout = 5000
Given path 'm', 'demo', 'products', '1'
And param mock_delay = 2000
When method get
Then status 200
* assert responseTime >= 2000
Flip the numbers β readTimeout = 1500 against mock_delay=3000 β and the step fails with java.net.SocketTimeoutException: Read timed out (real excerpt), which is precisely how you prove a client-side budget is enforced. mock_delay takes 0β5000 ms; ?mock_chaos=0.3 adds random failures if you want soak-style flakiness instead of scripted sequences.
karate mockKarate ships a mock server and it's genuinely good for what it is: scenarios match on pathMatches()/methodIs(), state lives in Background variables, and you get hot reload with -W. This stateful pet store works, verified on 2.1.2:
Feature: karate mock demo
Background:
* def pets = [{ id: 1, name: 'Rex' }, { id: 2, name: 'Spot' }]
* def nextId = 3
Scenario: pathMatches('/pets') && methodIs('get')
* def response = pets
Scenario: pathMatches('/pets') && methodIs('post')
* def pet = request
* pet.id = nextId
* def nextId = nextId + 1
* pets.push(pet)
* def response = pet
* def responseStatus = 201
java -jar karate.jar mock -m petmock.feature -p 8090
Things we hit while testing it, so you don't have to:
pets.add(pet) fails with TypeError: pets.add is not a function; use pets.push(pet).GET /pets?limit=1 returned the full array β pagination, filtering, sorting are all logic you write per resource./pets/3, restarted the mock: 404. Fine for a test run, gone when the process is.{"error":"no matching scenario"} β credit where due, that's a better miss than some tools' 502 pages.ss) β unlike several tools in this space that silently bind localhost only, teammates on your network can hit it.So: karate mock is the right tool when the suite and the mock travel together in one repo and one process lifetime. The hosted-sandbox job is different β a URL that exists before and after any process, where ?limit=3&sortBy=price, relations, mock auth, and failure params come built-in, and where the same data answers GraphQL too. You don't author list behaviors; you author data.
| karate mock | your real API | Mockbird | |
|---|---|---|---|
| Setup before the run | start process (same jar) | deploy / stage / seed | none, or one create scenario |
| List behaviors (paginate/filter/sort) | code you write | β real | β built-in params |
| Survives process restart | β memory only | β | β persisted |
| Shareable URL (teammates, CI, mobile) | your machine, while running | β | β public URL |
| Deterministic failure sequences | code you write | β please don't | mock_seq param |
| Works offline / airgapped | β | depends | β needs egress |
| Tests real contract drift | β | β its actual job | β it's a mock |
If the suite's job is contract-testing your real API, point Karate at your real API. karate mock beats us on airgapped runners and zero-latency loops. The hosted sandbox earns its keep when the test is about your client's behavior β retries, timeouts, pagination walking, error rendering β or when the backend doesn't exist yet. Projects have a 10,000 requests/day cap; a test suite barely dents it, but don't point karate-gatling load tests at us.
curl -sL -o karate.jar https://github.com/karatelabs/karate/releases/download/v2.1.2/karate-2.1.2.jar
curl -s https://mockbird.mockbird.workers.dev/m/demo/products?limit=3
β¦then paste the first feature above into demo.feature and run it. Or create your own project in one click. Free, no signup required. Full docs Β· machine-readable API index.
Related: the same patterns in Tavern/pytest, ephemeral projects in GitHub Actions (Karate's jar slots straight into that workflow), deterministic test data with snapshots, and testing loading and error states.
Verification: every feature file on this page was run verbatim with the Karate 2.1.2 standalone jar (downloaded fresh from the official GitHub release on 27 Aug 2026) against production, including the failure cases: the pets.add TypeError, the ignored ?limit=1, the post-restart 404, and the SocketTimeoutException are all real observed output. If a snippet here doesn't work, that's a bug: tell us.