Credit where due: Karate is one of the most loved test-automation frameworks there is β API tests, mocks, performance, and even UI automation unified behind one readable DSL, ~9k GitHub stars, MIT-licensed core, and very much alive: v2.1.2 shipped August 14, 2026, with commits landing the day we wrote this. Its built-in mock server is a genuinely clever idea β the same feature-file syntax you test with also authors the mock, which makes consumer-driven contracts feel natural. And unlike several tools we've put through this drill, the basics genuinely work: we ran v2.1.2 (one 12 MB fatjar, java -jar karate.jar mock -m mock.feature -p 8080), and in-session state was real (our POST persisted and a later GET returned it), responseDelay = 2000 measured a true 2.0 s, hot-reload via -W is built in, and an entirely unmatched path returns a clean 404 {"error":"no matching scenario"}. If your job is testing services with Karate and stubbing their dependencies in the same repo and idiom, keep doing exactly that.
This page is about a different job that Karate mocks get pressed into: a data-shaped sandbox API for a frontend, a prototype, or a teammate β the "just give me /products that behaves like a real backend" job. We wrote a 25-line CRUD feature file per their docs and put it through the same drill we use on every tool. It works β and it has walls.
Wall 1: every data behavior is code you author, scenario by scenario. A Karate mock is a program: Background: holds your state map, and each route is a Scenario: pathMatches('/products') && methodIs('post') block whose body you write in embedded JavaScript. Our feature file handled list/get/create/delete in ~25 lines β genuinely compact β but that's where the free lunch ends. GET /products?category=kitchen&limit=1 returned the entire collection, category ignored, limit ignored: query semantics exist only if you write a paramValue() branch for every param, on every resource. Filtering, sorting, pagination, search, relations β each one is more mock code for you to maintain and debug.
Wall 2: the 200-empty-body trap is one undefined variable away. In our matched pathMatches('/products/{id}') scenario, an unknown id made response undefined β and the server answered HTTP 200 with an empty body. res.json() throws, and your frontend's error handling never sees the 404 it was written for. (Fully unmatched paths do 404 cleanly in v2 β credit β but the ids-that-don't-exist case lives inside your matched scenario, where correctness is on you.)
Wall 3: it's in-memory β a restart loses everything. We POSTed a record, killed the JVM, started it again: the record was gone and the seed data was back. State lives exactly as long as one process. Fine for a test run that spawns the mock; fatal for a mock a team hits for a week.
Wall 4: there is no hosted option β the mock is a local JVM process. The fatjar needs a Java runtime installed, and the server runs on your machine. The moment a teammate, a phone on another network, a CI job against a deployed preview, or a workshop room needs to hit the mock, you're deploying and babysitting infrastructure yourself. (The core is free and MIT β Karate Labs monetizes IDE plugins and enterprise protocol add-ons, not hosting; there's no mock-hosting cloud to pay for even if you wanted one.)
Where you'd author a feature file and run a jar, create a hosted project instead β nothing to run, nothing to keep alive, no Java required:
curl -X POST https://mockbird.mockbird.workers.dev/api/projects \
-H 'content-type: application/json' -d '{"name":"my-api","preset":"ecommerce"}'
# β {"id":"<PID>","adminKey":"β¦","baseUrl":"https://mockbird.mockbird.workers.dev/m/<PID>",β¦}
# 4 resources, realistic seeded records, live at a public URL immediately
Your Background: seed map has a direct equivalent: import a db.json β {"products":[β¦your exact recordsβ¦]} β and your records are hosted verbatim, ids preserved. Then every query behavior from Wall 1 is just true, with zero scenarios authored:
# the params our feature file ignored (ran on the shared demo before publishing):
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?sortBy=price&order=desc&limit=2&select=name,price'
# β the two most expensive products, only the fields you asked for
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?inStock=true&limit=2&page=2'
# β filtered AND paginated
# writes persist β and survive every restart we don't have:
curl -X POST https://mockbird.mockbird.workers.dev/m/demo/products \
-H 'content-type: application/json' -d '{"name":"my product","price":9.99}'
# β {"name":"my product","price":9.99,"id":31}
curl https://mockbird.mockbird.workers.dev/m/demo/products/31 # β same record, tomorrow too
# and a missing record is an honest 404 β not a 200 with an empty body
| Karate mock | Mockbird | Notes |
|---|---|---|
java -jar karate.jar mock -m mock.feature -p 8080 | POST /api/projects | hosted URL instead of a JVM on localhost; nothing to operate, no Java |
Background: state map + seed | db.json import | records hosted verbatim, ids preserved; or typed fakes via presets + ?seed=N |
one Scenario: per method+path | generated REST endpoints | list/get/create/patch/delete on every resource, plus nested routes and relations |
hand-coded paramValue() branches | always-on query toolkit | filter, _gte/_lte/_ne/_like, sort, pagination, search, ?select= β zero authoring |
responseDelay = 2000 | ?mock_delay=2000 | honest: theirs works (we measured it) β ours needs no scenario, any endpoint, per request |
| authored error scenarios | ?mock_status=503, ?mock_seq=500,500,200, ?mock_chaos=0.3 | failure drills as query params β verified 503 + 500,500,200 on the demo |
| in-memory state, wiped on restart | persistent storage by default | deliberate resets via snapshots; pin per request with X-Mockbird-Snapshot |
fixed routes like /health | custom routes + templating | {{params}}, {{now}}, {{body.x}} β no JS to maintain |
inspecting karate.log | request inspector | last 50 requests with headers + bodies, in the dashboard |
| the feature file is the spec | generated OpenAPI, Postman, TS types | exported from the live schema at /m/<PID>/openapi.json etc. |
| Karate tests (the main event) | keep them | point them at the hosted URL β see below |
Karate the test framework and Mockbird solve different problems, and they work great together: your existing Karate tests run unchanged against a hosted project β no mock feature file, no process to spawn in CI, and the same URL your frontend and your teammate's phone are already using:
Feature: products API
Background:
* url 'https://mockbird.mockbird.workers.dev/m/<PID>'
Scenario: create then read back
Given path 'products'
And request { name: 'Spot check', price: 12.5 }
When method post
Then status 201
* def id = response.id
Given path 'products', id
When method get
Then status 200
And match response.name == 'Spot check'
Keep Karate mocks where they shine: contract-style stubs that live next to the Karate tests that consume them, in infrastructure you already run.
# everything our feature file would have needed hand-written branches for:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?page=2&limit=3'
# failure and latency simulation without authoring a scenario:
curl -i 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_status=503'
curl 'https://mockbird.mockbird.workers.dev/m/demo/products/1?mock_delay=2000'
# create your own β one click, no terminal:
# https://mockbird.mockbird.workers.dev/app#new=ecommerce
| Karate mock server | Mockbird | |
|---|---|---|
| Price | free, MIT core | free while in beta |
| Hosted URL others can hit | no β local JVM process; self-deploy to share | yes β 10,000 requests/project/day, no signup |
| Stateful CRUD | yes, if you code it β in-memory, wiped on restart (verified) | yes β persistent database, snapshots, restore |
| Pagination/filter/sort/search on lists | only what you hand-code per param, per resource | always on, every resource |
| Missing-record behavior | 200 + empty body unless you code the 404 (verified) | honest 404, always |
| Latency / failure simulation | responseDelay works (verified); failures = authored scenarios | mock_delay/mock_status/mock_seq/mock_chaos query params |
| Arbitrary logic in the mock | full embedded JS β anything you can write | templated custom routes only β theirs is stronger |
| Same-idiom tests + mocks | yes β the whole point, and it's lovely | no DSL; tests hit a URL |
| Generated OpenAPI/Postman/types of the mock | no | yes β all three, from the live schema |
| GraphQL | authorable by hand | generated endpoint β queries, mutations, relations |
| Works offline / no request caps | yes | no |
| Install footprint | 12 MB fatjar + a Java runtime | zero β it's a URL |
| Maintenance | very active β v2.1.2 Aug 2026 | actively developed |
Written by the Mockbird maker β bias disclosed. Where Karate genuinely wins: the unified test-automation DSL (API, mocks, performance, UI) is unique and excellent; mocks-as-consumer-driven-contracts next to the tests that consume them; full JavaScript programmability inside the mock; hot-reload; offline use with no request caps; clean 404s on unmatched paths; and working responseDelay β all free and MIT at the core. Every behavioral claim above was verified by us on August 26, 2026 against Karate v2.1.2 (official release fatjar, karate mock subcommand): the CRUD feature file, ignored query params, 200-empty on missing ids inside a matched scenario, the restart wipe, the 404 on unmatched paths, and the 2.0 s measured delay were all observed directly. If any of this changes, we'll update the page.
Full API reference in the docs. More guides: stubby4j alternative Β· MockServer alternative Β· WireMock Cloud alternative Β· mountebank alternative Β· Hoverfly alternative Β· Prism alternative Β· free mock API tools compared. Create your API β