← All guides

A mountebank alternative for when the mock should just be a URL

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.

First, the elephant: maintenance

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 packageLatest stableDownloads/week (Aug 18–24, 2026)
mountebank (original, frozen)2.9.1 β€” August 202338,742
@mbtest/mountebank (community fork)2.9.44,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.

The walls (each one verified on v2.9.4, August 2026)

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.

The 60-second switch

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 concepts β†’ Mockbird

mountebankMockbirdNotes
mb start + POST /impostersPOST /api/projectshosted URL instead of a local port; nothing to run
stub: predicate + is responsecustom routemethod/path/status/body/headers/contentType/delayMs + {{query.x}}/{{body.x}}/{{uuid}} templating
canned CRUD stubs (+ inject for state)real stateful resourcesPOST→GET-back persists; PUT/PATCH/DELETE; filters/sort/pagination/search built in
behaviors: [{wait: 2000}]?mock_delay=2000per-request, no config edit β€” measured 2.1 s on the shared demo
response cycling [500, 200]?mock_seq=500,200semantics 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=2any endpoint, on cue β€” guide
recordRequests: true + GET /imposters/:portrequest inspectoralways on, last 50 with headers + bodies, readable from tests via API
proxy record / playbackno equivalenthonest: a genuine mountebank strength
JavaScript injectno equivalent by designwe don't execute your code; templating covers the simple cases
predicates DSL (regex/xpath/jsonpath)exact match + _gte/_lte/_ne/_like operatorshonest: their matching DSL is far richer
tcp / smtp / grpc / ldap protocolsHTTP(S) onlyif you need wire protocols, keep mountebank
config files + mb saveserver-side persistence + snapshotssave/restore named data states; pin one per request with X-Mockbird-Snapshot

Try it in 10 seconds (shared demo, no setup)

# 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

Honest comparison

mountebankMockbird
Pricefree, open source (MIT)free while in beta
Protocolshttp/https/tcp/smtp + community extensions (grpc, ldap, ws…)HTTP(S) + GraphQL + SSE/WebSocket echo
Hosted URL others can hitno β€” self-host a node process; no hosted offering existsyes β€” 10,000 requests/project/day, no signup
Stateful CRUD (POST β†’ GET it back)no β€” canned responses; DIY via JS injectyes β€” persistent records, snapshots, restore
Unstubbed path behavior200 with empty body (verified v2.9.4)real 404 with a JSON error
Pagination/filter/sort/searchonly what you stub, per variantalways on, every resource
Record/playback proxyingexcellentnone
Matching DSLdeep β€” regex, xpath, jsonpath, custom JSexact + range/contains operators
Works offlineyesno
Maintenancecommunity transition since 2024; stable npm package frozen at Aug 2023 unless you switch to @mbtest/mountebankactively 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 β†’