# Traffic Parrot for Mountebank users

## Two standalone servers, one way of working

[Mountebank](https://www.mbtest.dev/) 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.

The JSON itself carries over through the [HTTP Import](https://trafficparrot.com/documentation/5.65.x/http.html#import-http-usage) page: upload the file `mb save` writes, or one imposter's JSON, review the preview and import it, and each stub of each `http` or `https` imposter becomes one or more mappings (one per `or` branch of its predicates and per response in a cycle), in the imposter's order with the first stub at the highest priority; `POST /http/management/importMappings` on the management port imports the same file without the preview. The imposters' ports all become Traffic Parrot's one virtual service port, which the import says in a warning when a file holds more than one. A stub using something with no mapping equivalent is skipped and named in a warning on the import page, and the imposter's other stubs still import: for example an `inject` predicate or response, a `decorate`, `shellTransform`, `copy` or `lookup` behavior, a predicate with `except` or on `requestFrom`, `form` or `ip`, or a `wait` given as a function. An imposter whose protocol is not `http` or `https` is skipped whole, and a config file that `mb --configfile` expands with EJS templates is not JSON, so run `mb save` and import the file it writes. The [concept-mapping table](https://trafficparrot.com/documentation/5.65.x/mountebank_users.html#concept-mapping) is what to use for the parts the import skips or cannot translate, and the [worked example](https://trafficparrot.com/documentation/5.65.x/mountebank_users.html#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](https://trafficparrot.com/documentation/5.65.x/jms.html) (ActiveMQ, RabbitMQ, Azure AMQP 1.0 and IBM® WebSphere MQ over JMS), [native IBM® MQ](https://trafficparrot.com/documentation/5.65.x/ibmmq.html), [gRPC](https://trafficparrot.com/documentation/5.65.x/grpc.html), [Thrift](https://trafficparrot.com/documentation/5.65.x/thrift.html), [file-based messages](https://trafficparrot.com/documentation/5.65.x/filemessage.html), [SOAP](https://trafficparrot.com/documentation/5.65.x/http.html#soap) and [WebSocket](https://trafficparrot.com/documentation/5.65.x/websocket.html) 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](https://trafficparrot.com/documentation/5.65.x/mountebank_users.html#multi-protocol) below.
- **A web user interface.** Browse, add and edit [mappings](https://trafficparrot.com/documentation/5.65.x/http.html#add-edit-http-mappings) in a form, read the live [request log](https://trafficparrot.com/documentation/5.65.x/http.html#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](https://trafficparrot.com/documentation/5.65.x/http.html#recording-http) into mapping files you can open in the editor, and [cleanup](https://trafficparrot.com/documentation/5.65.x/http.html#http-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](https://trafficparrot.com/tutorials/record-replay-http-requests-and-responses-to-create-api-mocks.html).
- **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](https://trafficparrot.com/documentation/5.65.x/dynamic.html): request data, JSONPath, XPath, regular expressions, CSV lookups and state, with no code execution. When you do need real code, the [external-process transformer](https://trafficparrot.com/documentation/5.65.x/dynamic.html#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](https://trafficparrot.com/documentation/5.65.x/http.html#import-http-usage) into mappings, [reports which operations your mocks cover](https://trafficparrot.com/documentation/5.65.x/http.html#http-coverage), and [validates mappings against the specification](https://trafficparrot.com/documentation/5.65.x/http.html#validate-cli) 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](https://blog.trafficparrot.com/2020/04/what-is-concurrent-floating-license.html) 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](https://trafficparrot.com/documentation/5.65.x/http.html), HTTP/2 included |
| SOAP | Over the HTTP imposter, matched as text | Built in: [SOAP recording and XML responses](https://trafficparrot.com/documentation/5.65.x/http.html#soap) |
| gRPC | Community protocol implementation | Built in: [gRPC](https://trafficparrot.com/documentation/5.65.x/grpc.html), driven from your `.proto` files |
| WebSocket | Community protocol implementation | Built in: [WebSocket](https://trafficparrot.com/documentation/5.65.x/websocket.html) |
| JMS message queues and topics | Not covered | Built in: [JMS](https://trafficparrot.com/documentation/5.65.x/jms.html) |
| Native IBM® MQ | Not covered | Built in: [native IBM® MQ](https://trafficparrot.com/documentation/5.65.x/ibmmq.html) |
| Thrift | Not covered | Built in: [Thrift](https://trafficparrot.com/documentation/5.65.x/thrift.html) |
| File-based messages | Not covered | Built in: [file-based messages](https://trafficparrot.com/documentation/5.65.x/filemessage.html), 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](https://trafficparrot.com/documentation/5.65.x/properties.html)), 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](https://trafficparrot.com/documentation/5.65.x/virtual_services.html#manage-add), 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](https://trafficparrot.com/documentation/5.65.x/user_guide.html#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](https://trafficparrot.com/documentation/5.65.x/request_matching.html#what-is-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](https://trafficparrot.com/documentation/5.65.x/http.html#add-edit-http-mappings) saves. |
| Predicates `equals`, `contains`, `startsWith`, `endsWith`, `matches`, `deepEquals`, `exists` | [Request matchers](https://trafficparrot.com/documentation/5.65.x/http.html#http-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](https://trafficparrot.com/documentation/5.65.x/request_matching.html#matchers-reference). |
| Predicates `and`, `or`, `not` | The same [logical operators](https://trafficparrot.com/documentation/5.65.x/request_matching.html#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](https://trafficparrot.com/documentation/5.65.x/http.html#add-edit-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](https://trafficparrot.com/documentation/5.65.x/http.html#request-log-near-misses) 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`](https://trafficparrot.com/documentation/5.65.x/http.html#file-backed-responses), which is where a recorded body goes. |
| A `responses` array cycled one entry per request, and `repeat` on a response | [Stub-level scenario state](https://trafficparrot.com/documentation/5.65.x/dynamic.html#built-in-transformers): 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](https://trafficparrot.com/documentation/5.65.x/http.html#recording-http): enter the Recording URL, click Start recording, run your traffic, and each response is a mapping file. Then [cleanup](https://trafficparrot.com/documentation/5.65.x/http.html#http-cleanup) rewrites the literal matchers into patterns and consolidates duplicates, which is the job `predicateGenerators` does at record time. [Existing mappings answer first](https://trafficparrot.com/documentation/5.65.x/http.html#recording-existing-mappings), as they do in `proxyOnce` mode. |
| The `proxy` response in `proxyTransparent` mode | The [passthrough proxy](https://trafficparrot.com/documentation/5.65.x/http.html#passthrough-proxy) for a whole virtual service, or a [proxy response](https://trafficparrot.com/documentation/5.65.x/http.html#add-edit-proxy) on one mapping, forwarding without recording. |
| The `inject` response, and the `decorate` behavior | A [Handlebars template](https://trafficparrot.com/documentation/5.65.x/dynamic.html) in the response body and headers for anything computed from the request or from data files, and the [external-process transformer](https://trafficparrot.com/documentation/5.65.x/dynamic.html#external-process-transformer) when only real code will do. Java teams can also [write their own helpers and transformers](https://trafficparrot.com/documentation/5.65.x/dynamic.html#write-your-own). |
| The `shellTransform` behavior | The [external-process transformer](https://trafficparrot.com/documentation/5.65.x/dynamic.html#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](https://trafficparrot.com/documentation/5.65.x/hoverfly-middleware.html) has the worked example. |
| The `copy` behavior: a value from the request placed into the response | [Request data in the template](https://trafficparrot.com/documentation/5.65.x/dynamic.html#request-data-http): `request.path`, `request.query`, `request.headers` and `request.body`, with [JSONPath](https://trafficparrot.com/documentation/5.65.x/dynamic.html#extract-jsonpath), [XPath](https://trafficparrot.com/documentation/5.65.x/dynamic.html#extract-xpath) and [regular-expression](https://trafficparrot.com/documentation/5.65.x/dynamic.html#extract-regex) helpers to pick the value out. |
| The `lookup` behavior: a row from a CSV file chosen by a request value | The [CSV helper](https://trafficparrot.com/documentation/5.65.x/dynamic.html#csv) and the [data source helpers](https://trafficparrot.com/documentation/5.65.x/dynamic.html#data-source), keyed by a value from the request in the same template. |
| The `wait` behavior | [Delays](https://trafficparrot.com/documentation/5.65.x/http.html#fault-delay-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](https://trafficparrot.com/documentation/5.65.x/http.html#fault-delay-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](https://trafficparrot.com/documentation/5.65.x/http.html#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](https://trafficparrot.com/documentation/5.65.x/http.html#verifying-requests) 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. A mapping posted to the API is written to a file when its JSON carries `"persistent": true`, as the example below does; without the flag it is held in memory and gone on reset or restart, unless it is saved from the mappings list first. [Export and import](https://trafficparrot.com/documentation/5.65.x/http.html#import-export-http-mappings) move a set of mappings as a ZIP. |
| `mb --configfile` with one file per environment | [Scenarios](https://trafficparrot.com/documentation/5.65.x/user_guide.html#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](https://trafficparrot.com/documentation/5.65.x/virtual_services.html#composite-scenarios) that combine several. |
| `mb --localOnly`, `--ipWhitelist` and `--allowInjection` | [Login and roles](https://trafficparrot.com/documentation/5.65.x/user_guide.html#role-based-access) 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](https://trafficparrot.com/documentation/5.65.x/start.html), run the [Docker image](https://trafficparrot.com/documentation/5.65.x/user_guide.html#running-docker), start and stop it from a build with the [Maven](https://trafficparrot.com/documentation/5.65.x/user_guide.html#maven-plugin) or [Gradle](https://trafficparrot.com/documentation/5.65.x/user_guide.html#gradle-plugin) plugin, or start it inside a JUnit test with the [Testcontainers module](https://trafficparrot.com/documentation/5.65.x/testcontainers.html). |

## 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:

```bash
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`:

```bash
curl -X POST http://localhost:8080/api/http/__admin/mappings \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "customers: GET /customers/123",
    "persistent": true,
    "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`:

```bash
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 because it was posted with `"persistent": true` (without the flag it is held in memory: serving at once, but gone on reset or restart), so you could equally have written that JSON to a file and started Traffic Parrot, filled in the [Add/Edit mapping](https://trafficparrot.com/documentation/5.65.x/http.html#add-edit-http-mappings) form and clicked Save, or uploaded the imposter JSON above on the [HTTP Import](https://trafficparrot.com/documentation/5.65.x/http.html#import-http-usage) page, which writes a mapping like it, named `customers stub 1`, for you. 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](https://trafficparrot.com/documentation/5.65.x/http.html#fault-delay-injection) 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](https://trafficparrot.com/documentation/5.65.x/dynamic.html) reads the same fields; if it read a file or a database, the [data source helpers](https://trafficparrot.com/documentation/5.65.x/dynamic.html#data-source) do; if it called out to something, the [external-process transformer](https://trafficparrot.com/documentation/5.65.x/dynamic.html#external-process-transformer) keeps that code in its own process.

## Where to go next

- **Request matching**: the matchers your predicates become, on [Request Matching](https://trafficparrot.com/documentation/5.65.x/request_matching.html) and the [HTTP matchers reference](https://trafficparrot.com/documentation/5.65.x/http.html#http-matchers).
- **Dynamic responses**: the helpers that replace `inject`, `copy` and `lookup`, on the [Dynamic Responses](https://trafficparrot.com/documentation/5.65.x/dynamic.html) page.
- **Recording and cleanup**: the [record and replay tutorial](https://trafficparrot.com/tutorials/record-replay-http-requests-and-responses-to-create-api-mocks.html) and [How to clean up recorded HTTP mappings](https://trafficparrot.com/tutorials/clean-up-recorded-http-mappings.html).
- **Middleware**: the [external-process middleware page](https://trafficparrot.com/documentation/5.65.x/hoverfly-middleware.html), written for Hoverfly users and just as applicable to a `shellTransform`.
- **Starting it from a test**: the [Testcontainers module](https://trafficparrot.com/documentation/5.65.x/testcontainers.html) and the [Maven plugin](https://trafficparrot.com/documentation/5.65.x/user_guide.html#maven-plugin).

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](https://trafficparrot.com/articles/best-service-virtualization-tools.html), and for the same exercise from the WireMock side, read [Traffic Parrot for WireMock users](https://trafficparrot.com/documentation/5.65.x/wiremock_users.html).
