Mountebank: Over-the-Wire Test Doubles
Mountebank is an open-source, cross-platform, multi-protocol test double tool designed to mock services over the wire. It enables developers to create configurable mock API endpoints for various protocols like HTTP, HTTPS, TCP, and SMTP. Mountebank primarily operates by creating "imposters" that listen on specified ports, responding to requests based on defined "stubs" which include predicates (conditions) and responses. This approach allows for isolated and repeatable testing of applications, reducing dependencies on external services and improving development efficiency. The project recently transitioned to a community-driven effort under the `mountebank-testing` GitHub organization, with the npm package name changing from `mountebank` to `@mbtest/mountebank`. It maintains a consistent release cadence, with several updates in the past year (v2.9.1 to v2.9.4). Key differentiators include its multi-protocol support, a powerful REST API for dynamic configuration, and the ability to extend functionality through JavaScript injection.
Common errors
-
Error: listen EADDRINUSE :::2525
cause Mountebank's administration port (2525) or an imposter's port is already in use by another application.fixChange the conflicting port in your Mountebank configuration, or stop the other application using that port. For imposters, specify a different `port` in your imposter configuration. For the admin API, use `mb --port <newPort>`. -
invalid injection
cause You are attempting to use JavaScript injection in an imposter (e.g., in a response `inject` block or predicate `inject`) without enabling the feature.fixRestart Mountebank with the `--allowInjection` command-line flag: `mb --allowInjection`. -
npm ERR! cb() never called!
cause Older versions of npm (v2) can have issues with `npm shrinkwrap` or `npm install` when `mountebank` (or `@mbtest/mountebank`) is a dependency, particularly due to deep dependency trees.fixUpgrade npm to version 3 or higher (`npm install -g npm@latest`). If stuck on npm v2, a workaround involves adding mountebank's top-level dependencies to your `package.json` and deleting `node_modules/mountebank/node_modules` between `npm install` and `npm shrinkwrap`.
Warnings
- breaking The official npm package name changed from `mountebank` to `@mbtest/mountebank` starting from version v2.9.2. Existing projects must update their `package.json` and installation commands (`npm install -g @mbtest/mountebank`).
- breaking Minimum Node.js version updated to 20 for core maintenance LTS in v2.9.4. Docker images now use Node 24 (active LTS). Running on older Node.js versions may lead to instability or failures.
- gotcha Using JavaScript injection (e.g., for dynamic responses or predicates) requires starting the Mountebank server with the `--allowInjection` flag. Without this flag, any imposter configuration using injection will fail with an 'invalid injection' error.
- gotcha Mountebank is not persistent by default. All configured imposters and recorded requests are lost when the server restarts unless you explicitly save and reload them using configuration files or the `--datadir` / `--impostersRepository` options.
Install
-
npm install mountebank -
yarn add mountebank -
pnpm add mountebank
Imports
- mb
npm install -g mountebank
npm install -g @mbtest/mountebank && mb
- mb.create
const mb = require('mountebank');const mb = require('@mbtest/mountebank'); const server = await mb.create({ port: 2525 }); - Imposter API
fetch('http://localhost:2525/imposters', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(imposterConfig) })
Quickstart
import { strict as assert } from 'assert';
import { exec } from 'child_process';
import util from 'util';
const execPromise = util.promisify(exec);
const imposterConfig = {
port: 4545,
protocol: 'http',
stubs: [
{
predicates: [{
equals: { path: '/test', method: 'GET' }
}],
responses: [
{ is: { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'Hello from Mountebank!' }) } }
]
}
]
};
async function runMountebankQuickstart() {
let mbProcess;
try {
console.log('1. Installing @mbtest/mountebank globally...');
await execPromise('npm install -g @mbtest/mountebank');
console.log(' Installed successfully.');
console.log('2. Starting Mountebank server...');
// Using `exec` to start mb in the background, ensure --allowInjection for future use cases
// and --loglevel debug for better visibility.
mbProcess = exec('mb --allowInjection --loglevel debug');
mbProcess.stdout.pipe(process.stdout);
mbProcess.stderr.pipe(process.stderr);
// Give Mountebank a moment to start up
await new Promise(resolve => setTimeout(resolve, 3000));
console.log(' Mountebank server started (admin UI at http://localhost:2525).');
console.log('3. Creating an HTTP imposter...');
await fetch('http://localhost:2525/imposters', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(imposterConfig)
});
console.log(' Imposter created on port 4545.');
console.log('4. Testing the mocked service...');
const response = await fetch('http://localhost:4545/test');
const data = await response.json();
assert.equal(response.status, 200, 'Expected status code 200');
assert.deepEqual(data, { message: 'Hello from Mountebank!' }, 'Expected specific mock response');
console.log(' Mocked service responded correctly:', data);
} catch (error) {
console.error('An error occurred:', error.message);
process.exit(1);
} finally {
if (mbProcess) {
console.log('5. Stopping Mountebank server...');
// Terminate the mountebank process gracefully
await execPromise('mb stop');
console.log(' Mountebank server stopped.');
}
}
}
runMountebankQuickstart();