Traffic Parrot for Mountebank users

Part of Switching from another tool, the hub for every migration page.

« Back to documentation home

Two standalone servers, one way of working

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

Why Mountebank users move to Traffic Parrot

Teams that outgrow Mountebank usually do so for one of five reasons:

  • Messaging and RPC in the same server as HTTP. Traffic Parrot virtualises JMS (ActiveMQ, RabbitMQ, Azure AMQP 1.0 and IBM® WebSphere MQ over JMS), native IBM® MQ, gRPC, Thrift, file-based messages, SOAP and WebSocket alongside HTTP, in one process with one configuration model. Mountebank's core covers HTTP, HTTPS, TCP and SMTP; message queues are not part of it, and protocols such as gRPC and WebSocket come from community protocol implementations that run as separate programs. See One server for every protocol below.
  • A web user interface. Browse, add and edit mappings in a form, read the live request log with its near-miss explanations of why a request did not match, and start and stop recording with a button. Testers, analysts and developers who do not want to hand-write JSON can read and change the mocks.
  • Recording that turns into a mock you can keep. A Mountebank proxy stub records the responses it forwards and replays them as literal stubs. Traffic Parrot records into mapping files you can open in the editor, and cleanup rewrites a literal recording into a flexible mock: URL patterns instead of exact paths, templated responses, duplicates consolidated. See the record and replay tutorial.
  • Extensibility that stays sandboxed. Mountebank's 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.
  • Contract-driven mocks. Traffic Parrot imports an OpenAPI specification into mappings, reports which operations your mocks cover, and validates mappings against the specification in CI. Mountebank stubs are hand-written or recorded.

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.

One server for every protocol

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.

Mapping Mountebank concepts onto Traffic Parrot

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.

One stub, both ways

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.

Where to go next

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.