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:
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json
{
"port": 3001,
"protocol": "http"
}
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json
{
"port": 3000,
"protocol": "http",
"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):
GET /test?q=mountebank&page=1 HTTP/1.1
Host: localhost:3000
GET /imposters/3000 HTTP/1.1
Host: localhost:<%= port %>
{
"stubs": [
{
"predicates": [{
"caseSensitive": true,
"deepEquals": {
"query": {
"q": "mountebank",
"page": "1"
}
}
}],
"responses": [{
"is": { ... }
"_proxyResponseTime": 5
}
}]
},
{
"responses": [{
"proxy": {
"to": "http://origin-server.com ",
"predicateGenerators": [{
"matches": { "query": true },
"caseSensitive": true
}]
}
}]
}
]}
DELETE /imposters/3000
Host: localhost:<%= port %>
Just as with predicates, we could have also specified only the query key we cared about:
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json
{
"port": 3000,
"protocol": "http",
"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:
GET /test?q=mountebank&page=1 HTTP/1.1
Host: localhost:3000
GET /imposters/3000 HTTP/1.1
Host: localhost:<%= port %>
{
"stubs": [
{
"predicates": [{
"equals": {
"query": { "q": "mountebank" }
}
}],
"responses": [{
"is": { ... }
"_proxyResponseTime": 5
}
}]
},
{
"responses": [{
"proxy": {
"to": "http://origin-server.com ",
"predicateGenerators": [{
"matches": {
"query": { "q": "mountebank" }
}
}]
}
}]
}
]}
DELETE /imposters/3000
Host: localhost:<%= port %>
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:
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json
{
"port": 3000,
"protocol": "http",
"stubs": [{
"responses": [{
"proxy": {
"to": "http://origin-server.com ",
"predicateGenerators": [{
"matches": { "body": true },
"xpath": { "selector": "//number" }
}]
}
}]
}]}
We'll send it the following XML body:
POST / HTTP/1.1
Host: localhost:3000
<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):
GET /imposters/3000 HTTP/1.1
Host: localhost:<%= port %>
{
"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" }
}]
}
}]
}
]}
DELETE /imposters/3000
Host: localhost:<%= port %>
Similarly, the following stub looks for number elements in JSON:
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json
{
"port": 3000,
"protocol": "http",
"stubs": [{
"responses": [{
"proxy": {
"to": "http://origin-server.com ",
"predicateGenerators": [{
"matches": { "body": true },
"jsonpath": { "selector": "$..number" }
}]
}
}]
}]}
We'll send it the following JSON body:
POST / HTTP/1.1
Host: localhost:3000
[
{ "number": 1 },
{ "number": 2 },
{ "number": 3 }
]
Again, the generated predicate gets scoped to the jsonpath matches.
GET /imposters/3000 HTTP/1.1
Host: localhost:<%= port %>
{
"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" }
}]
}
}]
}
]}
DELETE /imposters/3000
Host: localhost:<%= port %>
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
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.
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json
{
"port": 3000,
"protocol": "http",
"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.
GET /imposters/3000 HTTP/1.1
Host: localhost:<%= port %>
{
"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}"
}]
}
}]
}
]}
DELETE /imposters/3000
Host: localhost:<%= port %>
Support ignoring certain keys in predicateGenerators
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
Content-Type: application/json
{
"port": 3000,
"protocol": "http",
"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
GET /path?limit=100&enhanced=true&startDate=2017-09-07&endDate=2017-10-11 HTTP/1.1
Host: localhost:3000
The saved predicate should not have the startDate
key.
GET /imposters/3000 HTTP/1.1
Host: localhost:<%= port %>
{
"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" }
}]
}
}]
}
]}
DELETE /imposters/3000
Host: localhost:<%= port %>
DELETE /imposters/3001
Host: localhost:<%= port %>