Part of Switching from another tool, the hub for every migration page.
« Back to documentation homeMountebank and Traffic Parrot are built on the same idea: a test double is a separate process that speaks the real protocol over the wire, you describe it as JSON, you drive it through an HTTP administration API from any language, and it can record a real service as well as play a script. If you have been running Mountebank, the habits that matter carry over unchanged. Nothing here asks you to embed a library in your test process or to write mocks in a particular language.
What does not carry over is the JSON itself. A Mountebank imposter definition is not a Traffic Parrot mapping, and Traffic Parrot does not import imposter files. Moving a mock is a translation, stub by stub, from one vocabulary to the other. The concept-mapping table is that translation, and the worked example shows one stub written both ways.
The two tools also differ in what they set out to cover. Mountebank is an open-source Node.js project whose built-in protocols are HTTP, HTTPS, TCP and SMTP. Traffic Parrot is a commercial Java product that virtualises HTTP together with the messaging and RPC protocols enterprise systems depend on, and adds a web user interface, recording, clean-up and contract tooling on top. The next section is about which of those differences matter for your team.
Teams that outgrow Mountebank usually do so for one of five reasons:
inject
responses and its decorate and shellTransform behaviors run
arbitrary code, so they are off until the whole process is started with
--allowInjection. In Traffic Parrot most of what inject is used
for is a Handlebars template: request data, JSONPath, XPath,
regular expressions, CSV lookups and state, with no code execution. When you do need real
code, the external-process
transformer runs any executable with the exchange as JSON on stdin and stdout, and it
is off by default, limited to one allow-listed directory and killed on a timeout.
Traffic Parrot is also a supported commercial product: there is a team behind it that answers questions, fixes defects and adds protocols, and licensing is by concurrent (floating) instances rather than per developer. Mountebank is MIT-licensed and free, which is the right trade for many teams; the sections below are for the ones whose needs have moved past it.
A Mountebank imposter is a port plus a protocol, so a system that calls an HTTP API, puts a message on a queue and reads a reply from a mainframe needs the HTTP half from Mountebank and the rest from somewhere else, each with its own configuration style and lifecycle. Traffic Parrot virtualises all of them in one running instance, with the same request-matching and response-templating model across protocols, so stubbing a queue after stubbing an endpoint is a small step rather than a new tool.
| Protocol | Mountebank | Traffic Parrot |
|---|---|---|
| HTTP and HTTPS | Built in | Built in: HTTP, HTTP/2 included |
| SOAP | Over the HTTP imposter, matched as text | Built in: SOAP recording and XML responses |
| gRPC | Community protocol implementation | Built in: gRPC, driven from your .proto files |
| WebSocket | Community protocol implementation | Built in: WebSocket |
| JMS message queues and topics | Not covered | Built in: JMS |
| Native IBM® MQ | Not covered | Built in: native IBM® MQ |
| Thrift | Not covered | Built in: Thrift |
| File-based messages | Not covered | Built in: file-based messages, local, SFTP and FTP |
| Raw TCP and SMTP | Built in | Not covered |
The last row is the honest one to read first: if the imposters you rely on are raw TCP or SMTP, Traffic Parrot is not a drop-in for those. For everything above it, one instance replaces the HTTP imposter and the tools you had bolted on beside it.
Every Traffic Parrot cell links to the page that documents it. Where a Mountebank feature has no single equivalent, the cell says what to use instead.
| Mountebank | Traffic Parrot |
|---|---|
| An imposter: one port, one protocol, a list of stubs |
The HTTP virtual service: one port, trafficparrot.virtualservice.http.port
(8081 by default, see the
configuration reference), serving every HTTP
mapping in the selected scenario. Several imposters on different ports usually
become one virtual service whose mappings are told apart by path; when you need a
port per dependency, add
further virtual services, each with
its own HTTP and HTTPS ports.
|
The mb administration API on port 2525:
POST /imposters, GET /imposters/:port,
DELETE /imposters/:port |
The management port, trafficparrot.gui.http.port
(8080 by default), which hosts the web user interface and the
management APIs. The WireMock-compatible
admin API is under /api/http/__admin/ there, so
POST /api/http/__admin/mappings creates a mapping from JSON.
|
A stub: predicates plus responses |
A mapping: a
request matcher plus a response. One mapping file per
mapping, under the scenario's mappings directory, in the same JSON the
editor saves.
|
Predicates equals, contains, startsWith,
endsWith, matches, deepEquals,
exists |
Request matchers on the URL, method, headers,
query parameters, cookies and body: equalTo, contains,
matches (a regular expression), equalToJson,
equalToXml, matchesJsonPath, matchesXPath,
absent and more. The full list is on the
matchers reference.
|
Predicates and, or, not |
The same logical operators,
and, or and not, composed in the mapping JSON
or in the editor.
|
A stub with no predicates, or the imposter's defaultResponse, answering
whatever nothing else matched |
A catch-all mapping with a lower priority than the specific ones (the default priority is 5, and 1 is the highest). A request that no mapping matches gets an HTTP 900 response and a near-miss entry in the request log instead of a silent empty 200. |
The is response: statusCode, headers,
body |
The mapping's response: status, headers and
body, or bodyFileName for a body kept as a
file under __files, which
is where a recorded body goes.
|
A responses array cycled one entry per request, and
repeat on a response |
Stub-level scenario state: give the
mappings in the sequence the same scenarioName, each its
requiredScenarioState, and the one that moves the sequence on a
newScenarioState. The state is explicit, so a test can read it back and
reset it rather than counting calls.
|
The proxy response in proxyOnce or
proxyAlways mode, with predicateGenerators deciding what
the recorded stubs match on |
Recording: enter the Recording URL, click
Start recording, run your traffic, and each response is a mapping file. Then
cleanup rewrites the literal matchers into
patterns and consolidates duplicates, which is the job
predicateGenerators does at record time.
Existing mappings answer first,
as they do in proxyOnce mode.
|
The proxy response in proxyTransparent mode |
The passthrough proxy for a whole virtual service, or a proxy response on one mapping, forwarding without recording. |
The inject response, and the decorate behavior |
A Handlebars template in the response body and headers for anything computed from the request or from data files, and the external-process transformer when only real code will do. Java teams can also write their own helpers and transformers. |
The shellTransform behavior |
The external-process
transformer: any executable, the request and response as a JSON envelope on
stdin, the mutated response on stdout. Enabled per instance with
trafficparrot.http.externalprocess.enabled and an allow-listed
trafficparrot.http.externalprocess.allowedDir, then opted into per
mapping. The middleware page has the
worked example.
|
The copy behavior: a value from the request placed into the response |
Request data in the template:
request.path, request.query,
request.headers and request.body, with
JSONPath,
XPath and
regular-expression helpers to pick the
value out.
|
The lookup behavior: a row from a CSV file chosen by a request value |
The CSV helper and the data source helpers, keyed by a value from the request in the same template. |
The wait behavior |
Delays on the mapping: fixed, uniform or log-normal, and a chunked dribble that spreads the body over time. |
The fault response (a connection reset, random bytes then close) |
Faults on the mapping: nine of them, from a connection reset and an empty response to a malformed chunk, an under-declared content length and dripping bytes. |
recordRequests and the requests array on
GET /imposters/:port, used to verify what was called |
The request log, always on, in the user
interface and over the API:
GET /api/http/requests lists received requests and
POST /api/http/requests/count answers a
verification with a count. Unmatched
requests come with the closest mapping and why it did not match.
|
mb save and mb replay, or --datadir, to
keep imposters across restarts |
Nothing to do: mappings are files, read from and written to the scenario's mappings and __files directories as you record and edit, so they survive a restart and go into version control as they are. Export and import move a set of mappings as a ZIP. |
mb --configfile with one file per environment |
Scenarios: a named set of mapping directories, one selected at a time, switched in the user interface or with the scenarios API, and composite scenarios that combine several. |
mb --localOnly, --ipWhitelist and
--allowInjection |
Login and roles on the web user interface and its APIs, and the two external-process properties above for code execution, which stays off unless both are set. |
Starting mb from a script, a Docker image or a test lifecycle hook |
Start the downloaded distribution, run the Docker image, start and stop it from a build with the Maven or Gradle plugin, or start it inside a JUnit test with the Testcontainers module. |
A customer lookup, mocked in Mountebank as an HTTP imposter on port 4545 with one stub, created through the administration API:
curl -X POST http://localhost:2525/imposters \
-H 'Content-Type: application/json' \
-d '{
"port": 4545,
"protocol": "http",
"name": "customers",
"stubs": [{
"predicates": [{ "equals": { "method": "GET", "path": "/customers/123" } }],
"responses": [{
"is": {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": "{\"id\": 123, \"name\": \"Example customer\"}"
}
}]
}]
}'
The same mock in Traffic Parrot is one mapping, created through the management port and
served on the virtual service port. The predicate becomes the request matcher
and the is response becomes the response:
curl -X POST http://localhost:8080/api/http/__admin/mappings \
-H 'Content-Type: application/json' \
-d '{
"name": "customers: GET /customers/123",
"request": { "method": "GET", "urlPath": "/customers/123" },
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": "{\"id\": 123, \"name\": \"Example customer\"}"
}
}'
Call it on the virtual service port and the answer is the one the imposter gave on port 4545:
curl -i http://localhost:8081/customers/123
Three things to notice. The mapping now exists as a file in the selected scenario's
mappings directory, so you could equally have written that JSON to a file and
started Traffic Parrot, or filled in the
Add/Edit mapping form and clicked Save. The
name is what the mapping list and the request log show, so it is worth giving.
And a wait behavior of 500 milliseconds on the Mountebank response is
"fixedDelayMilliseconds": 500 in the Traffic Parrot response, one
of the delay and fault attributes the editor
also exposes.
Where a stub's response was an inject function, look at what the function read:
if it read the request, a template reads the same fields; if it read a
file or a database, the data source helpers do; if it
called out to something, the
external-process transformer keeps
that code in its own process.
inject,
copy and lookup, on the
Dynamic Responses page.
shellTransform.
Still weighing up the wider field rather than moving a specific Mountebank setup? This page stays a migration reference. For a comparison across tools, protocols, deployment and licensing, read Best Service Virtualization Tools, and for the same exercise from the WireMock side, read Traffic Parrot for WireMock users.