Credit where due: mountebank earned its reputation. It's the open-source service-virtualization tool that competes with commercial suites on protocol diversity β http, https, raw tcp (text and binary), smtp in core, with community extensions for grpc, ldap, websockets and more. Capital One famously moved a huge performance-testing program onto it after crushing their enterprise mocking software. If you need to virtualize a TCP wire protocol or record-and-replay a downstream service, mountebank is still the tool, and nothing on this page changes that.
This page is about the rest of us: teams using mountebank to mock a plain HTTP/JSON API for frontend work and integration tests β where you're paying the full config-as-code tax for none of the multi-protocol payoff.
In May 2024 the original author announced development was ending β in his words, "there are no easy exit ramps for hobbyist open source." The project has since moved to a community organization (mountebank-testing), whose README currently describes a project in transition, with pull-request merging paused while CI/CD infrastructure moves off personal accounts (checked August 2026).
The part that bites in practice is the npm split. The package everyone's scripts install is frozen:
| npm package | Latest stable | Downloads/week (Aug 18β24, 2026) |
|---|---|---|
mountebank (original, frozen) | 2.9.1 β August 2023 | 38,742 |
@mbtest/mountebank (community fork) | 2.9.4 | 4,742 |
That's roughly 89% of installs still landing on a package that hasn't shipped a stable release since 2023. If you stay on mountebank, at minimum switch to npm install -g @mbtest/mountebank β that's the honest first answer, and the docs now live at mbtest.dev.
We installed the community fork and drove it with a plain imposter β a GET /products stub returning a canned list and a POST /products stub returning a canned 201. Every claim below is the actual observed behavior.
Wall 1: every response is a script, so writes are theater. POST /products returned our canned 201 {"id":2,"name":"created"} β and the next GET /products returned the same one-widget list as before. Nothing was created; the 201 is a recording. mountebank can fake state with JavaScript inject functions, but then you're hand-writing a database in stub config.
Wall 2: anything you forget to stub returns 200-empty. An unmatched path β including the completely obvious GET /products/1 next to our stubbed GET /products β answered HTTP 200 with an empty body. That's worse than a 404: res.json() throws, and your app's error handling never sees an error status. Every route, every ID, every edge is config you write and maintain.
Wall 3: query params are silently ignored. GET /products?page=2&limit=1&price_gte=100 matched the same equals: {path: "/products"} predicate and returned the same canned body. Pagination, sorting, filtering β each one is more predicates and more canned variants, per resource, forever.
Wall 4: it's a localhost node process. mb binds local ports. The moment a teammate, a deployed preview, CI, or your phone needs the mock, you're provisioning and babysitting a server β and unlike WireMock or Mockoon, there is no hosted mountebank to upgrade to at any price.
Where you'd write an imposter config and POST http://localhost:2525/imposters, create a hosted project instead β same "everything is an API" spirit, no process to run:
curl -X POST https://mockbird.mockbird.workers.dev/api/projects \
-H 'content-type: application/json' -d '{"name":"my-api"}'
# β {"id":"g3cxybzbx3","adminKey":"β¦","baseUrl":"https://mockbird.mockbird.workers.dev/m/g3cxybzbx3",β¦}
curl -X POST https://mockbird.mockbird.workers.dev/api/projects/<PID>/resources \
-H 'x-admin-key: <KEY>' -H 'content-type: application/json' \
-d '{"name":"products","fields":[{"name":"name","type":"words"},{"name":"price","type":"price"}],"seed":5}'
# β 5 realistic seeded records, live at /m/<PID>/products
Now the behaviors you'd hand-stub are just true:
# writes persist (ran against this API before publishing):
curl -X POST β¦/m/<PID>/products -H 'content-type: application/json' -d '{"name":"added by test","price":1.99}'
# β {"name":"added by test","price":1.99,"id":6}
curl β¦/m/<PID>/products/6 # β the same record. It's real.
# missing records are real 404s, not 200-empty:
curl -i β¦/m/<PID>/products/999 # β HTTP 404 {"error":"not found"}
# and the query toolkit needs zero predicates:
curl 'β¦/m/<PID>/products?page=2&limit=3&sortBy=price&order=desc&price_gte=100'
Have a fixed/legacy payload that really is best as a canned stub? Custom routes are one curl and support any content type, status, headers, latency and response templating β we verified an XML route with delayMs: 1500 answered in a measured 1.6 s:
curl -X POST β¦/api/projects/<PID>/routes -H 'x-admin-key: <KEY>' \
-H 'content-type: application/json' \
-d '{"method":"GET","path":"/legacy/soap-status","status":200,
"contentType":"application/xml","body":"<status>OK</status>","delayMs":1500}'
| mountebank | Mockbird | Notes |
|---|---|---|
mb start + POST /imposters | POST /api/projects | hosted URL instead of a local port; nothing to run |
stub: predicate + is response | custom route | method/path/status/body/headers/contentType/delayMs + {{query.x}}/{{body.x}}/{{uuid}} templating |
canned CRUD stubs (+ inject for state) | real stateful resources | POSTβGET-back persists; PUT/PATCH/DELETE; filters/sort/pagination/search built in |
behaviors: [{wait: 2000}] | ?mock_delay=2000 | per-request, no config edit β measured 2.1 s on the shared demo |
response cycling [500, 200] | ?mock_seq=500,200 | semantics differ: theirs cycles 500,200,500,200β¦; ours plays the sequence once and the last value sticks (500,200,200) β better for retry drills |
| error drills via extra stubs | ?mock_status=503, ?mock_chaos=0.3, ?mock_ratelimit=2 | any endpoint, on cue β guide |
recordRequests: true + GET /imposters/:port | request inspector | always on, last 50 with headers + bodies, readable from tests via API |
| proxy record / playback | no equivalent | honest: a genuine mountebank strength |
JavaScript inject | no equivalent by design | we don't execute your code; templating covers the simple cases |
| predicates DSL (regex/xpath/jsonpath) | exact match + _gte/_lte/_ne/_like operators | honest: their matching DSL is far richer |
| tcp / smtp / grpc / ldap protocols | HTTP(S) only | if you need wire protocols, keep mountebank |
config files + mb save | server-side persistence + snapshots | save/restore named data states; pin one per request with X-Mockbird-Snapshot |
# query params that actually work β no predicates written:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?page=2&limit=3'
# a real 404 where mountebank's default gives you 200-empty:
curl -i 'https://mockbird.mockbird.workers.dev/m/demo/products/999'
# wait-behavior equivalent, per request (measured 2.1s):
curl 'https://mockbird.mockbird.workers.dev/m/demo/products/1?mock_delay=2000'
# fail-then-recover sequence for retry logic:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_seq=500,200&mock_seq_key=me1'
# β 500 on the first call, 200 after
| mountebank | Mockbird | |
|---|---|---|
| Price | free, open source (MIT) | free while in beta |
| Protocols | http/https/tcp/smtp + community extensions (grpc, ldap, wsβ¦) | HTTP(S) + GraphQL + SSE/WebSocket echo |
| Hosted URL others can hit | no β self-host a node process; no hosted offering exists | yes β 10,000 requests/project/day, no signup |
| Stateful CRUD (POST β GET it back) | no β canned responses; DIY via JS inject | yes β persistent records, snapshots, restore |
| Unstubbed path behavior | 200 with empty body (verified v2.9.4) | real 404 with a JSON error |
| Pagination/filter/sort/search | only what you stub, per variant | always on, every resource |
| Record/playback proxying | excellent | none |
| Matching DSL | deep β regex, xpath, jsonpath, custom JS | exact + range/contains operators |
| Works offline | yes | no |
| Maintenance | community transition since 2024; stable npm package frozen at Aug 2023 unless you switch to @mbtest/mountebank | actively developed |
Written by the Mockbird maker β bias disclosed. Where mountebank genuinely wins: protocol diversity (nothing else open-source virtualizes raw TCP and SMTP like it), record/playback proxying, a matching DSL we don't approach, arbitrary logic via JS injection, offline use with no request caps, and proven performance at bank scale. Every behavioral claim above was verified by us in August 2026 against @mbtest/mountebank v2.9.4 (imposter on :4545, stubs as described); npm download numbers are the Aug 18β24, 2026 week from npm's public API; maintenance status is from the mountebank-testing README and the fork notices on GitHub. If any of this changes, we'll update the page.
Full API reference in the docs. More guides: stubby4j alternative Β· MockServer alternative Β· Hoverfly alternative Β· WireMock Cloud alternative Β· Mockoon alternative Β· Prism alternative Β· mock any custom endpoint Β· Karate mock server alternative Β· free mock API tools compared. Create your API β