mountebank

mountebank - over the wire test doubles


the apothecary

Configuring mountebank

The basic command line structure is:

mb [command=start] [options...]

mountebank commands

As mountebank has grown in functionality, the number of commands and options has increased significantly. The links below attempt to simplify that configuration based on the core mountebank commands.

mb start

start is the default mb command, meaning:

mb start [options]

is identical to

mb [options]

Running mb by itself, without any options, will start up the API on port 2525. It will also spin up this website on http://localhost:2525/, giving you accurate documentation for the version of mountebank you're running, as the official site only contains the latest docs. The following options are available:

Option Description Default
--port 2525 The port to run the main mountebank server on 2525
--host mbserver.local The hostname to bind the main mountebank server to all hosts
--datadir .mbdb The root directory for persisting all imposter changes. When used, mountebank will start all imposters saved in the directory initially and persist all operations to disk in real time, significantly reducing the memory footprint.

This option allows you to scale imposters with multiple processes (running on multiple hosts behind a load balancer to avoid port collision), with the state of all processes synced in real time. All mountebank processes will need to share the same volume.

Without this option, all configuration will be in memory. Keeping everything in memory can be a significant performance hit when there is a lot of test data, for example, during proxy recording.
--configfile imposters.ejs If present, mountebank will load the contents of the specified file. See below for details.

If both the datadir and configfile options are used, mountebank will initially load all imposters from the datadir and then add all from the configfile. This means that when the same imposter port is saved in both places, what is in the datadir will be immediately overwritten by what's in the configfile!

N/A
--formatter path/to/module Historically, mountebank supported EJS templating when using the configfile option, and was limited to saving all configuration in a single file when calling mb save. For backwards compatibility, that remains the default option, even though EJS has subsequently made breaking changes.

A custom formatter allows you to save test data in whatever format you want (including in ways that convert between other service virtualization products). See below for more details. In the context of mb start, the formatter will be used to parse the configfile.

mountebank-formatters
--noParse By default, mountebank will render config files through EJS templating to allow modularizing rich configuration. Use this flag if you aren't using templating and have special character sequences in your configuration that cause rendering errors. false
--logfile mb.log The file for mountebank to store the logs in mb.log
--loglevel debug The logging level, one of debug, info, warn, error info
--nologfile Prevent logging to the filesystem false
--log Advanced logging configuration, when you want to customize the log formats. While you can pass the JSON string on the command line, it's easier to put it in the rcfile. If you pass log, the simpler logging configuration options (loglevel, logfile, nologfile) will be ignored.

You can set the format to "json" to log all fields as JSON, or set it to a string to customize the format. The supported fields are:

  • %level
  • %timestamp
  • %message
{
  "level": "info",
  "transports": {
    "console": {
      "colorize": true,
      "format": "%level: %message"
    },
    "file": {
      "path": "mb.log",
      "format": "json"
    }
  }
}
--allowInjection mountebank supports JavaScript injection for predicates, stub responses, behavior decoration, wait behavior functions and tcp request resolution, but they are disabled by default. Including this parameter will enable them.

Note that allowing injection means that an attacker can run random code on the machine running mb. Please see the security page for tips on securing your system.

false
--localOnly Only accept requests from localhost. You should ALWAYS do this when running mountebank with allowInjection directly on your developer machine, but will need to use ipWhitelist otherwise (or if running in Docker), false
--ipWhitelist A pipe-delimited string of remote IP addresses to whitelist (local IP addresses will always be allowed). Any request to the primary mb socket or an imposter socket that isn't whitelisted will be dropped. *, representing all IP addresses
--origin A safe origin for CORS requests. Use the flag multiple times to enable multiple origins. false, which disables CORS to prevent CSRF attacks.
--protofile File to load custom protocol implementations from. protocols.json
--rcfile .mbrc The run commands file containing startup configuration. The rcfile format is a JSON-equivalent representation of the command line option. For example, the following command line is very complex:
mb --port 3000 --allowInjection --origin 'http://first.com' --origin 'http://second.com' \
--log '{ "level": "warn", "transports": { "console": { "format": "json" } } }'
You could simplify it by putting the configuration in a file and running
mb start --rcfile .mbrc
The file .mbrc would look like the following:
{
  "port": 3000,
  "allowInjection": true,
  "origin": ["http://first.com", "http://second.com"],
  "log": {
    "level": "warn",
    "transports": {
      "console": {
        "format": "json"
      }
    }
  }
}
When the same option is listed in both the rcfile and the command line, the command line option takes precedence.
N/A
--debug Include a matches array with each stub in the body of a GET imposter response for debugging why a particular stub did or did not match a request. Every time a response from the stub is used, a match will be added containing the request, the response configuration, the actual generated response (even if it is proxied), and the overall processing time. false
--pidfile The file where the process id is stored for the mb stop command mb.pid
--version Print the version out to the console and exit. N/A
--help Show help for the command N/A
mb stop
mb stop [options]

Stops mb, shutting down all open sockets. Generally, mb stop must be run from the same directory as the mb start command, although that is configurable with the pidfile option:

Option Description Default
--pidfile The path to the pidfile, which stores the process id of the running mb process. Must match the pidfile passed in the mb start command. mb.pid
--rcfile .mbrc The run commands file containing startup configuration (a JSON-equivalent representation of the command line arguments). When the same option is listed in both the rcfile and the command line, the command line option takes precedence. N/A
--help Show help for the command N/A
mb restart
mb restart [options]

The restart command is equivalent to running mb stop followed by mb start. To ensure a clean stop, the --pidfile must match the one passed to the original mb start process. By default, this means you must run restart in the same working directory you originally ran the start command from.

The restart command has all the same command line options as the start command.

mb save
mb save [options]

While you can always use the API to capture the current configuration of imposters, mountebank provides a convenient command line mechanism to save the configuration into a file that can be used to start a subsequent process of mb using the --configfile command line option. With a running mb process operating on port 3000, you can execute the following command:

mb save --port 3000 --savefile saved.json --removeProxies

All of the parameters are optional with the defaults listed in the table below. You could then restart mountebank with the following command:

mb restart --port 3000 --configfile saved.json

The save command takes the following options:

Option Description Default
--port 2525 The port of the running the mountebank server 2525
--host mbserver.local The hostname of the running mountebank server localhost
--savefile saved.json The file to save imposters to mb.json
--formatter path/to/module Historically, mountebank was limited to saving all configuration in a single file when calling mb save. A custom formatter allows you to save test data in whatever format you want (including in ways that convert between other service virtualization products). See below for more details.

mountebank-formatters
--removeProxies Remove proxies from the saved configuration, useful when you want to switch from record to replay mode. Corresponds to the removeProxies API query parameter false
--rcfile .mbrc The run commands file containing startup configuration (a JSON-equivalent representation of the command line arguments). When the same option is listed in both the rcfile and the command line, the command line option takes precedence. N/A
--help Show help for the command N/A
mb replay
mb replay [options]

The replay command is a convenience that removes all proxies, effectively switching from record mode to replay mode. Assuming mountebank is running on port 3000, you would run the following command:

mb replay --port 3000

That will reset the imposter configuration by sending a PUT command to /imposters based on the current configuration, excluding the proxies (using the ?removeProxies=true query parameter). The following options are available:

Option Description Default
--port 2525 The port of the running the mountebank server 2525
--host mbserver.local The hostname of the running mountebank server localhost
--rcfile .mbrc The run commands file containing startup configuration (a JSON-equivalent representation of the command line arguments). When the same option is listed in both the rcfile and the command line, the command line option takes precedence. N/A
--help Show help for the command N/A
mb help
mb help

Lists basic command line help. You can get more detailed help for a command with the following:

mb command --help

Saving test data: config files and formatters

Note that mb is not persistent by default. Absent the datadir option, stopping and restarting mb will lose all stubs and all requests. The following two mb commands work in tandem to allow you to save and reload test data:

mb save --savefile mb.json --formatter path/to/module
mb restart --configfile mb.json --formatter path/to/module

The configfile sends a PUT command to /imposters. View the JSON contract to see what the contents should look like.

The formatter is optional. If specified, it gives you total control over the format of the test data saved to disk, which allows you to split the content in separate files or convert to a format used by other service virtualization tools. The links below describe the default formatter for configuration files as well as how to define a custom formatter.

Default config file parsing

The default formatter for mountebank allows you to use EJS (version 2.x) templates to split the test data into multiple files. This is particularly useful for separating out JavaScript injection functions and XML or JSON HTTP response bodies because you store them as multi-line files and rely on templating to turn them into JSON-friendly single line strings. It remains the default for backwards compatibility, even though EJS 3.x has made breaking changes.

mountebank will pass a stringify function into your templates that allows you to put multi-line strings in separate files. The example below is loosely based on the response injection example described on the Injection page, and shows the use of the stringify function. The path passed in to stringify is relative to the root file referenced in the configfile command line parameter. You can also pass a custom object, referenced as data in child templates, as the second parameter of stringify. This is useful if you want to reuse the same template but add some dynamic data. For an example, look at the stringify call in templates/originServer.ejs below, and using the custom field in templates/originXMLResponse.ejs.

Assuming the files below are in a relative directory called templates, you can initialize mb with the following command:

mb --configfile templates/imposters.ejs --allowInjection --localOnly

templates/imposters.ejs

{
  "imposters": [
    <% include originServer.ejs %>,
    <% include proxyServer.ejs %>
  ]
}

templates/originServer.ejs

{
  "port": 5555,
  "protocol": "http",
  "name": "origin",
  "stubs": [
    {
      "predicates": [{ "contains": { "headers": { "Content-Type": "xml" } } }],
      "responses": [{ "is": { "body": "<%- stringify('originXMLResponse.ejs', { value: 'first }) %>" }}]
    },
    {
      "responses": [{ "inject": "<%- stringify('originServerResponse.ejs') %>" }]
    }
  ]
}

templates/originXMLResponse.ejs

<rootNode>
  <childNode><%= data.value %></childNode>
</rootNode>

templates/originServerResponse.ejs

(request, state, logger) => {
    logger.info('origin called');
    state.requests = state.requests || 0;
    state.requests += 1;
    return {
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ count: state.requests })
    };
}

templates/proxyServer.ejs

{
  "port": 4546,
  "protocol": "http",
  "name": "proxy",
  "stubs": [
    {
      "responses": [{ "inject": "<%- stringify('counter.ejs') %>" }],
      "predicates": [{
        "equals": {
          "method": "GET",
          "path": "/counter"
        }
      }]
    },
    {
      "responses": [{ "inject": "<%- stringify('proxy.ejs') %>" }]
    }
  ]
}

templates/counter.ejs

function (request, state) {
    var count = state.requests ? Object.keys(state.requests).length : 0,
        util = require('util');

    return {
        body: util.format('There have been %s proxied calls', count)
    };
}

templates/proxy.ejs

function (request, state, logger, callback) {
    var cacheKey = request.method + ' ' + request.path;

    if (typeof state.requests === 'undefined') {
        state.requests = {};
    }

    if (state.requests[cacheKey]) {
        logger.info('Using previous response');
        callback(state.requests[cacheKey]);
    }

    var http = require('http'),
        options = {
            method: request.method,
            hostname: 'localhost',
            port: 5555,
            path: request.path,
            headers: request.headers
        },
        httpRequest = http.request(options, response => {
            var body = '';
            response.setEncoding('utf8');
            response.on('data', chunk => {
                body += chunk;
            });
            response.on('end', () => {
                var stubResponse = {
                        statusCode: response.statusCode,
                        headers: response.headers,
                        body
                    };
                logger.info('Successfully proxied: ' + JSON.stringify(stubResponse));
                state.requests[cacheKey] = stubResponse;
                callback(stubResponse);
            });
        });
    httpRequest.end();
}
Custom formatters

A formatter is a CommonJS module that exports two functions: load, used to load the configuration when the configfile option is passed to mb start, and save, used to save the configuration for mb save. The formatter gives you total control over how the test data is stored, allowing you to improve readability (which suffers by default from JSON's single line requirement for strings) or to convert between formats of other service virtualization tools.

The default formatter that ships with mountebank is described above ("Default config file parsing"). The example below shows a simple (and silly) formatter that encodes all data as Base64.

'use strict';

function encode (obj) {
    return Buffer.from(JSON.stringify(obj)).toString('base64');
}

function decode (text) {
    return Buffer.from(text, 'base64').toString('utf8');
}

function load (options) {
    const fs = require('fs'),
        contents = fs.readFileSync(options.configfile, { encoding: 'utf8' });
    return JSON.parse(decode(contents));
}

function save (options, imposters) {
    const fs = require('fs');

    if (options.customName && imposters.imposters.length > 0) {
        imposters.imposters[0].name = options.customName;
    }
    fs.writeFileSync(options.savefile, encode(imposters));
}

module.exports = { load, save };

All CLI options are passed to both the load and save functions, allowing you to define custom options specific to your formatter. You can see an example of this in the save function, which takes a customName option and makes it the saved name of the first imposter.

The code above shows a synchronous implementation, but mountebank will accept a promise return value.

Assuming the module is saved as 'customFormatter.js', the following commands will use it for saving and loading:

mb save --savefile mb.json --formatter customFormatter
mb restart --configfile mb.json --formatter customFormatter