← All guides

Karate API testing with a real backend to hit

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.

1. A suite that passes in 60 seconds

# 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.

2. An ephemeral seeded backend, created by the suite itself

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.

3. Deterministic retry tests: retry until meets mock_seq

Karate 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.

4. Timeout budgets with configure readTimeout

Scenario: 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.

5. An honest look at karate mock

Karate 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:

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.

6. Honest comparison

karate mockyour real APIMockbird
Setup before the runstart process (same jar)deploy / stage / seednone, 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 sequencescode you write✘ please don'tmock_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.

Try it now

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.