Recording a rich set of test data through proxying requires also capturing the appropriate predicates from the request, so that saved responses are only replayed when the requests are similar. The predicateGenerators field defines the template for the generated predicates. Each object in the predicateGenerators array takes the following fields:

Parameter Default Type Description
matches {} object The fields that need to be equal in subsequent requests to replay the saved response. Set the field value true to generate a predicate based on it. Nested fields, as in JSON fields or HTTP headers, are supported as well, as long as the leaf keys have a true value. If you set the parent object key (e.g. query) to true, the generated predicate will use deepEquals, requiring the entire object graph to match.
caseSensitive false boolean Determines if the match is case sensitive or not. This includes keys for objects such as query parameters.
except "" string Defines a regular expression that is stripped out of the request field before matching.
xpath null object Defines an object containing a selector string and, optionally, an ns object field that defines a namespace map. The predicate's scope is limited to the selected value in the request field.
jsonpath null object Defines an object containing a selector string. The predicate's scope is limited to the selected value in the request field.
predicateOperator deepEquals or equals string Allows you to override the predicate operator used in the generated predicate. This is most often used to substitute an exists operator, e.g., for whether the given xpath expression exists in the incoming request or not. At times, it may be useful to use a contains operator if future requests can add more information to the field.
inject null string Defines a JavaScript function that allows programmatic creation of the predicates.
ignore {} object Use this option to ignore specific key of field from request based on match field.

With the exception of matches and inject, the fields correspond to the standard predicate parameters. Each object in the predicateGenerators array generates an object in the newly created stub's predicates array. You can decide how strictly you want the generated predicates to match object fields. The following example matches the root query field in its entirety:

"stubs": [{
  "responses": [{
    "proxy": {
      "to": "http://origin-server.com",
      "predicateGenerators": [{
        "matches": { "query": true },
        "caseSensitive": true
      }]
    }
  }]
}]

This will generate a deepEquals predicate at the same level, which requires that all keys and values in the querystring match (although the order can be different). We added the caseSensitive parameter, which will also require the cases of the query keys and values to match. If the incoming request is to /test?q=mountebank&page=1, the following stub will be generating (the saved response is elided for clarity):

"stubs": [
  {
    "predicates": [{
      "caseSensitive": true,
      "deepEquals": {
        "query": {
          "q": "mountebank",
          "page": "1"
        }
      }
    }],
    "responses": [{
      "is": { ... }
    }]
  },
  {
    "responses": [{
      "proxy": {
        "to": "http://origin-server.com",
        "predicateGenerators": [{
          "matches": { "query": true },
          "caseSensitive": true
        }]
      }
    }]
  }
]

Just as with predicates, we could have also specified only the query key we cared about:

"stubs": [{
  "responses": [{
    "proxy": {
      "to": "http://origin-server.com",
      "predicateGenerators": [{
        "matches": {
          "query": { "q": "mountebank" }
        }
      }]
    }
  }]
}]

The same request to /test?q=mountebank&page=1 now generates a more limited predicate:

"stubs": [
  {
    "predicates": [{
      "equals": {
        "query": { "q": "mountebank" }
      }
    }],
    "responses": [{
      "is": { ... }
    }]
  },
  {
    "responses": [{
      "proxy": {
        "to": "http://origin-server.com",
        "predicateGenerators": [{
          "matches": {
            "query": { "q": "mountebank" }
          }
        }]
      }
    }]
  }
]

xpath

The xpath and jsonpath predicate parameters work by limiting the scope of the generated predicate to the matching value in the proxied request. If the selector matches multiple values in the proxied request, they will all be part of the generated predicate, using standard predicate array matching rules. For example, following stub will look for number tags in the XML body:

"stubs": [{
  "responses": [{
    "proxy": {
      "to": "http://origin-server.com",
      "predicateGenerators": [{
        "matches": { "body": true },
        "xpath": { "selector": "//number" }
      }]
    }
  }]
}]

We'll send it the following XML body:

<doc>
  <number>1</number>
  <number>2</number>
  <number>3</number>
</doc>

Rather than matching the entire body, the generated predicate will require all three values matching the xpath selector in the original proxied request to be present (in any order):

"stubs": [
  {
    "predicates": [{
      "xpath": { "selector": "//number" },
      "deepEquals": { "body": ["1", "2", "3"] }
    }],
    "responses": [{
      "is": { ... }
    }]
  },
  {
    "responses": [{
      "proxy": {
        "to": "http://origin-server.com",
        "predicateGenerators": [{
          "matches": { "body": true },
          "xpath": { "selector": "//number" }
        }]
      }
    }]
  }
]

jsonpath

Similarly, the following stub looks for number elements in JSON:

"stubs": [{
  "responses": [{
    "proxy": {
      "to": "http://origin-server.com",
      "predicateGenerators": [{
        "matches": { "body": true },
        "jsonpath": { "selector": "$..number" }
      }]
    }
  }]
}]

We'll send it the following JSON body:

[
  { "number": 1 },
  { "number": 2 },
  { "number": 3 }
]

Again, the generated predicate gets scoped to the jsonpath matches.

"stubs": [
  {
    "predicates": [{
      "jsonpath": { "selector": "$..number" },
      "deepEquals": { "body": [1, 2, 3] }
    }],
    "responses": [{
      "is": { ... }
    }]
  },
  {
    "responses": [{
      "proxy": {
        "to": "http://origin-server.com",
        "predicateGenerators": [{
          "matches": { "body": true },
          "jsonpath": { "selector": "$..number" }
        }]
      }
    }]
  }
]

inject

In advanced scenarios, you need more fine-grained control over the creation of the predicates. In such scenarios, you can use the inject option.

The inject option requires the --allowInjection command line option.

The inject field takes a string representing a JavaScript function that is expected to return an array of predicate objects. Since it provides full control over the predicate generation, the inject option will ignore any other parameters, like xpath. The function accepts a single object parameter, which contains the following fields:

Field Description
request The entire request object, containing all request fields
logger A logger object with debug, info, warn, and error functions to write to the mountebank logs.

In this example, we'll add a predicate if a specific header exists. Here's the injection function fully expanded:


function (config) {
  const predicate = { exists: { headers: { 'X-Transaction-Id': false } } };
  if (config.request.headers['X-Transaction-Id']) {
    config.logger.debug('Requiring X-Transaction-Id header to exist in predicate');
    predicate.exists.headers['X-Transaction-Id'] = true;
  }
  return [predicate];
}

First let's add the imposter. The function must be passed as a single string, which makes it largely unreadable inline.

"stubs": [{
  "responses": [{
    "proxy": {
      "to": "http://origin-server.com",
      "predicateGenerators": [{
        "inject": "function (config) {\n  const predicate = { exists: { headers: { 'X-Transaction-Id': false } } };\n  if (config.request.headers['X-Transaction-Id']) {\n    config.logger.debug('Requiring X-Transaction-Id header to exist in predicate');\n    predicate.exists.headers['X-Transaction-Id'] = true;\n  }\n  return [predicate];\n}"
      }]
    }
  }]
}]

We'll send it the following HTTP request:

POST / HTTP/1.1
Host: localhost:3000
X-Transaction-Id: 100

SUCCESS

Since the X-Transaction-Id header was passed, the generated predicate requires it to exist to replay the response later.

"stubs": [
  {
    "predicates": [{
      "exists": { "headers": { "X-Transaction-Id": true } }
    }],
    "responses": [{
      "is": { ... }
    }]
  },
  {
    "responses": [{
      "proxy": {
        "to": "http://origin-server.com",
        "predicateGenerators": [{
          "inject": "function (config) {\n  const predicate = { exists: { headers: { 'X-Transaction-Id': false } } };\n  if (config.request.headers['X-Transaction-Id']) {\n    config.logger.debug('Requiring X-Transaction-Id header to exist in predicate');\n    predicate.exists.headers['X-Transaction-Id'] = true;\n  }\n  return [predicate];\n}"
        }]
      }
    }]
  }
]

ignore

Support ignoring certain keys in predicateGenerators

"stubs": [
  {
    "responses": [{
      "proxy": {
        "to": "http://origin-server.com",
        "predicateGenerators": [{
          "matches": { "query": true },
          "ignore": { "query": "startDate" }
        }]
      }
    }]
  }
]

Then we get a request /path?limit=100&enhanced=true&startDate=2017-09-07&endDate=2017-10-11

The saved predicate should not have the startDate key.

"stubs": [
  {
    "predicates": [{
      "deepEquals": { "query": { "limit": "100", "enhanced": "true", "endDate": "2017-10-11" } }
    }],
    "responses": [{
      "is": { ... }
    }]
  },
  {
    "responses": [{
      "proxy": {
        "to": "http://origin-server.com",
        "predicateGenerators": [{
            "matches": { "query": true },
            "ignore": { "query": "startDate" }
        }]
      }
    }]
  }
]