mountebank

mountebank - over the wire test doubles

Fork me on GitHub

the apothecary

Command Line

Running mb by itself will start up the API (and this site) on port 2525. Here are the options for mb:

Option Description Default
command One of start, stop, restart, save (which saves off the current imposter configuration), or help (which shows the usage information).

Note that stop and restart must either be run from the same directory as the start command, or be provided the --pidfile parameter to let mb know where to find the pidfile.

start
--port 2525 The port to run the main mountebank server on. 2525
--configfile imposters.ejs If present, mountebank will load the contents of the specified file. See below for details. N/A
--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. You can view the contents on the logs page. Please include this with any support requests after running with --loglevel debug. mb.log
--loglevel debug The logging level, one of debug, info, warn, error info
--nologfile Prevent logging to the filesystem false
--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 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 that isn't whitelisted will be dropped. *, representing all IP addresses
--mock mountebank supports mock verification by remembering the requests made against each stub. Note that this represents a memory leak for any long running mb process, as requests are never forgotten. false
--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. false
--pidfile The file where the pid is stored for the stop command mb.pid
--version Print the version out to the console and exit. N/A
--savefile saved.json The file to save imposters to when using the save command mb.json
--removeProxies Remove proxies from the saved configuration (only relevant when using the save command). Corresponds to the removeProxies API query parameter false

Note that mb is not persistent. Stopping and restarting mb will lose all stubs and all requests.

Saving mountebank configuration

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 above. You could then restart mountebank with the following command:

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

Config Files

Sometimes it's more convenient to load imposters via a config file rather than loading them on a per-test basis through the API. The --configfile option supports that by sending a PUT command to /imposters. View the JSON contract to see what the contents should look like.

Creating one file containing a set of complex configurations for multiple imposters can be unwieldy. mb supports using EJS templates, which allow you to put contents into separate files and use an EJS include directive to merge the contents into one file. 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.

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. You'll note that stringify takes a mysterious parameter named filename. Unfortunately, this is a required parameter, one that is required to be satisfied by the hidden variable named filename, and is simply a bit of magic that mountebank isn't clever enough to find a way to hide. The variable is passed in by mb and used to resolve relative paths.

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

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(filename, 'originXMLResponse.ejs') %>" }}]
    },
    {
      "responses": [{ "inject": "<%- stringify(filename, 'originServerResponse.ejs') %>" }]
    }
  ]
}

templates/originXMLResponse.ejs


<rootNode>
  <childNode>first</childNode>
  <childNode>second</childNode>
  <childNode>third</childNode>
</rootNode>

templates/originServerResponse.ejs


function (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(filename, 'counter.ejs') %>" }],
      "predicates": [{
        "equals": {
          "method": "GET",
          "path": "/counter"
        }
      }]
    },
    {
      "responses": [{ "inject": "<%- stringify(filename, '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, function (response) {
            var body = '';
            response.setEncoding('utf8');
            response.on('data', function (chunk) {
                body += chunk;
            });
            response.on('end', function () {
                var stubResponse = {
                        statusCode: response.statusCode,
                        headers: response.headers,
                        body: body
                    };
                logger.info('Successfully proxied: ' + JSON.stringify(stubResponse));
                state.requests[cacheKey] = stubResponse;
                callback(stubResponse);
            });
        });
    httpRequest.end();
}