- Mountebank provides configure virtual services, which are called imposters.
- Each imposter represents a socket that acts as the virtual service and accepts connections from the real service you are testing.
- Spinning up and shutting down imposters is a lightweight operation.
- Predicates help route requests on the way in.
- Responses generate the responses on the way out.
- Behaviors postprocess the responses before shipping them over the wire
- Goto Node.js downloads and download Node.js for Windows
- LTS
- Current
- Run the Node.js Installer(Windows)
- Welcome to the Node.js setup wizard
- Select Next
- End-User License Agreement (EULA)
- Check I accept the terms in the License Agreement
- Select Next
- Destination Folder
- Select Next
- Custom Setup
- Select Next
- Ready to install Node.js
- Select Install
- Note: This step requires Administrator privileges.
- If prompted, authenticate as an Administrator.
- Installing Node.js
- Let the installer run to completion.
- Completed the Node.js Setup Wizard
- Click Finish
- Welcome to the Node.js setup wizard
- Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"- Install Node.js
brew install node- Run command
node -v- Update npm
npm install npm -g- Install mountebank
npm install mountebank -gPOST /products?page=2&itemsPerPage=2 HTTP/1.1
Host: api.petstore.com
Content-Type: application/json
{
"key": "asdul7890"
}{
"method": "POST",
"path": "/products",
"query": {
"page": 2,
"itemsPrePage": 2
},
"headers": {
"Host": "api.petstore.com",
"Content-Type": "application/json"
},
"body": "{\n \"key\": \"asdul7890\"\n}"
}HTTP/1.1 200 OK
Date: Sun, 05 Apr 2020 10:10:10 GMT
Content-Type: application/json
{
"key": "asdul7890"
}{
"statusCode": 200,
"headers": {
"Date": "Sun, 05 Apr 2020 10:10:10 GMT",
"Content-Type": "application/json"
},
"body": "{\n \"key\": \"asdul7890\"\n}"
}{
"statusCode": 200,
"headers": {
"Content-Type": "text/plain"
},
"body": "Hello, World!"
}mbopen web browser then go to http://localhost:2525
curl
curl http://localhost:2525/imposterscurl
curl -X POST http://localhost:2525/imposters --data '
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"responses": [
{
"is": {
"statusCode": 200,
"headers": { "Content-Type": "text/plain" },
"body": "Hello, World!"
}
}
]
}
]
}'curl
curl -X DELETE http://localhost:2525/imposters/3000response
info: [mb:2525] DELETE /imposters/3000
info: [http:3000] Ciao for nowlist imposters
curl http://localhost:2525/imposterscurl
curl -X DELETE http://localhost:2525/imposterscreate file call hello-world.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"responses": [
{
"is": {
"statusCode": 200,
"headers": {
"Content-Type": "text/plain"
},
"body": "Hello, World!"
}
}
]
}
]
}mb start --configfile hello-world.jsonThe is response type, which is the fundamental building block for a stub.
- hello-world.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"responses": [
{
"is": {
"statusCode": 200,
"headers": {
"Content-Type": "text/plain"
},
"body": "Hello, World!"
}
}
]
}
]
}- default-response.json
{
"protocol": "http",
"port": 3001,
"name": "Default Response",
"defaultResponse": {
"statusCode": 400,
"headers": {
"Connection": "Keep-Alive",
"Content-Length": 0
}
},
"stubs": [
{
"responses": [
{
"is": {
"body": "BOOM!!!"
}
}
]
}
]
}- cycling-through-response.json
{
"protocol": "http",
"port": 3002,
"stubs": [
{
"responses": [
{
"is": {
"body": "1"
}
},
{
"is": {
"body": "2"
}
},
{
"is": {
"body": "3"
}
}
]
}
]
}{
"imposters": [
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"responses": [
{
"is": {
"statusCode": 200,
"headers": {
"Content-Type": "text/plain"
},
"body": "Hello, World!"
}
}
]
}
]
},
{
"protocol": "http",
"port": 3001,
"name": "Default Response",
"defaultResponse": {
"statusCode": 400,
"headers": {
"Connection": "Keep-Alive",
"Content-Length": 0
}
},
"stubs": [
{
"responses": [
{
"is": {
"body": "BOOM!!!"
}
}
]
}
]
},
{
"protocol": "http",
"port": 3002,
"stubs": [
{
"responses": [
{
"is": {
"body": "1"
}
},
{
"is": {
"body": "2"
}
},
{
"is": {
"body": "3"
}
}
]
}
]
}
]
}imposters.ejs
hello-world.json
default-response.json
cycling-through-response.json- imposters.ejs
{
"imposters": [
<
%
-
include
hello-world.json
%
>,
<
%
-
include
default-response.json
%
>,
<
%
-
include
cycling-through-response.json
%
>
]
}Mountebank uses a templating language called EJS (https://ejs.co/)
{
"port": "<port>",
"protocol": "http",
"stubs": [
{
"predicates": [],
"responses": [
{
"is": {
"statusCode": "<statusCode>",
"headers": {},
"body": {}
}
}
]
}
]
}Can be used with the following
- Path
- HTTP Method
- HTTP Header
- Query
- Body
- Plan Text
- JSON
- XML
matches-predicate.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"predicates": [
{
"equals": {
"method": "POST"
}
},
{
"startsWith": {
"body": "smart"
}
},
{
"endsWith": {
"body": "robot"
}
}
],
"responses": [
{
"is": {
"body": "Hello, smart robot"
}
}
]
}
]
}Mountebank matches
{
"predicates": [
{
"startsWith": {
"body": "smart"
}
},
{
"contains": {
"body": "alpha3"
}
},
{
"endsWith": {
"body": "robot"
}
}
]
}match with regular expressions
{
"predicates": [
{
"matches": {
"body": "^text to match"
}
},
{
"matches": {
"body": ".*text to match.*"
}
},
{
"matches": {
"body": "text to match$"
}
}
]
}path-identifier-predicate.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"predicates": [
{
"matches": {
"path": "/items/\\d+"
}
}
],
"responses": [
{
"is": {
"body": {
"name": "43 Piece Dinner Set",
"price": 12.95,
"quantity": 10
}
}
}
]
}
]
}The metacharacters used here, \d+, represent one or more digits, so the pattern will match /items/123 and /items/2 but not items/robot.
request-field-predicate.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"predicates": [
{
"equals": {
"query": {
"q": "robot"
}
}
}
],
"responses": [
{
"is": {
"body": {
"items": [
{
"name": "TA Robot",
"price": 109.99,
"quantity": 50
},
{
"name": "AlphaBot2",
"price": 71.0,
"quantity": 73
}
]
}
}
}
]
}
]
}deep-equals-predicate.json
{
"deepEquals": {
"query": {
"q": "robot",
"page": 1
}
}
}With this predicate, a query string of ?q=robot&page=1 would match, but a query string of ?q=robot&page=1&sort=desc wouldn’t.
GET /items?q=smart&q=robot
multivalued-field-predicate-01.json
requires exact match
{
"predicates": [
{
"deepEquals": {
"query": {
"q": [
"smart",
"robot"
]
}
}
}
]
}GET /items?q=smart&q=robot
multivalued-field-predicate-02.json
requires these elements to be present
{
"predicates": [
{
"equals": {
"query": {
"q": [
"smart",
"robot"
]
}
}
}
]
}There’s one more primitive predicate to look at. The exists predicate tests for either the existence or nonexistence of
a request field.
For example, you may decide that for testing purposes, you want to verify that the service handles an HTTP challenge
correctly (represented by a 401 sta- tus code) when the Authorization request header is missing, without worrying about
whether the credentials stored in the Authorization header are correct, as shown here:
exists-predicate.json
{
"predicates": [
{
"exists": {
"headers": {
"Authorization": false
}
}
}
],
"responses": [
{
"is": {
"statusCode": 401
}
}
]
}conjunction-without-and-predicate.json
{
"predicates": [
{
"startsWith": {
"body": "smart"
}
},
{
"endsWith": {
"body": "robot"
}
}
]
}You can reduce the array to a single element by using the and predicate.
conjunction-with-and-predicate.json
{
"predicates": [
{
"and": [
{
"startsWith": {
"body": "smart"
}
},
{
"endsWith": {
"body": "robot"
}
}
]
}
]
}| Operator | Description |
|---|---|
| equals | Requires the request field to equal the predicate value |
| deepEquals | Performs nested set equality on object request fields |
| contains | Requires the request field to contain the predicate value |
| startsWith | Requires the request field to start with the predicate value |
| endsWith | Requires the request field to end with the predicate value |
| Matches | Requires the request field to match the regular expression provided as the predicate value |
| exists | Requires the request field to exist as a nonempty value (if true) or not (if false) |
| not | Inverts the subpredicate |
| or | Requires any of the subpredicates to be satisfied |
| and | Requires all of the subpredicates to be satisfied |
Predicates are case-insensitive by default.
case-sensitive-predicate.json
{
"predicates": [
{
"equals": {
"query": {
"q": "robot"
}
}
}
]
}vs
{
"predicates": [
{
"equals": {
"query": {
"q": "robot"
}
},
"caseSensitive": true
}
]
}POST /items
{
"name": "riderX",
"price": 100,
"quantity": 50
}direct-json-predicate.json
{
"predicates": [
{
"equals": {
"body": {
"name": "riderX"
}
}
}
],
"responses": [
{
"is": {
"statusCode": 201,
"headers": {
"Location": "/items/123"
}
}
}
]
}POST /shipping
{
"items": [
{
"name": "TA Robot",
"price": 109.99,
"quantity": 50,
"location": "Bangkok"
},
{
"name": "AlphaBot2",
"price": 71.0,
"quantity": 73,
"location": "Covid-19 Zone"
}
]
}select-a-json-value-with-jsonpath-predicate.json
{
"predicates": [
{
"equals": {
"method": "POST"
}
},
{
"equals": {
"path": "/shipping"
}
},
{
"jsonpath": {
"selector": "$.items[(@.length-1)].location"
},
"equals": {
"body": "Covid-19 Zone"
}
}
],
"responses": [
{
"is": {
"statusCode": 400
}
}
]
}ref: JSONPath syntax
POST /shipping
<items>
<item name="TA Robot">
<price>109.99</price>
<location>Bangkok</location>
</item>
<item name="AlphaBot2">
<price>71.50</price>
<location>Covid-19 Zone</location>
</item>
</items>select-a-xml-value-with-xpath-predicate.json
{
"predicates": [
{
"equals": {
"method": "POST"
}
},
{
"equals": {
"path": "/shipping"
}
},
{
"xpath": {
"selector": "//item[@name='AlphaBot2']/location"
},
"equals": {
"body": "Covid-19 Zone"
}
}
],
"responses": [
{
"is": {
"statusCode": 400
}
}
]
}- Predicates help route requests on the way in.
- Responses generate the responses on the way out.
- Behaviors post-process the responses before shipping them over the wire
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"predicates": [],
"responses": [
{
"is": {},
"_behaviors": {
"decorate": {},
"wait": {},
"copy": []
}
}
]
}
]
}Decoration allows you to post-process the response.
- An inject response to a decorate behavior
{
"responses": [
{
"inject": "function (request, state, logger) {}"
}
]
}{
"responses": [
{
"is": {
"body": {}
},
"_behaviors": {
"decorate": "function (request, response, logger) {}"
}
}
]
}file decorate.json
mb start --allowInjection --configfile decorate.json{
"protocol": "http",
"port": 3000,
"stubs": [
{
"responses": [
{
"is": {
"statusCode": 201,
"headers": { "Content-Type": "application/json" },
"body": {}
},
"_behaviors": {
"decorate": "function (request, response, logger) { const item = JSON.parse(request.body); response.body.message = item.name"
}
}
]
}
]
}file sleep.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"responses": [
{
"is": {
"statusCode": 201,
"headers": { "Content-Type": "application/json" },
"body": { "name": "sleep" }
},
"_behaviors": {
"wait": 3000
}
}
]
}
]
}file repeating_a_response.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"predicates": [
{
"matches": { "path": "/items/1" }
}
],
"responses": [
{
"is": {
"body": {
"name": "43 Piece Dinner Set",
"price": 12.95
}
},
"_behaviors": {
"repeat": 3
}
},
{
"is": {
"body": {
"name": "RiderX",
"price": 2.95
}
}
},
{
"is": {
"body": {
"name": "Alpha Bot",
"price": 33.95
}
},
"_behaviors": {
"repeat": 5
}
}
]
}
]
}You can always add dynamic data to a response through an inject response, or through the decorate and shellTransform behaviors. But two additional behaviors support inserting certain types of dynamic data into the response without the overhead of programmatic control.
- The copy behavior accepts an array, which means you can make multiple replacements in the response.
- Each replacement should use a different token, and each one can select from a different part of the request.
- You never specify where the token is in the response. That’s by design. You could have put the token in the headers or even the status- Code, and mountebank would replace it.
request
GET /items/1 HTTP/1.1 Host: localhost:3000
response
{
"id": 1,
"name": "43 Piece Dinner Set",
"price": 12.95
}file replacing_content_in_the_response.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"is": {
"body": {
"id": "$ID",
"name": "43 Piece Dinner Set",
"price": 12.95
}
},
"_behaviors": {
"copy": [
{
"from": "path",
"into": "$ID",
"using": {
"method": "regex",
"selector": "\\d+$"
}
}
]
}
}
]
}- \d A digit, 0–9 (you have to double-escape the backslash in JSON)
- \w A word character
- + One or more times
- $ The end of the string
items/(\\w+)
items/123
['items/123', '123']file using_a_grouped_match.json
{
"protocol": "http",
"port": 3000,
"stubs": [
{
"responses": [
{
"is": {
"body": {
"id": "$ID[1]",
"name": "43 Piece Dinner Set",
"price": 12.95
}
},
"_behaviors": {
"copy": [
{
"from": "path",
"into": "$ID",
"using": {
"method": "regex",
"selector": "items/(\\w+)"
}
}
]
}
}
]
}
]
}mb start --configfile <configfile>.json --pidfile &mb stopmb restart --configfile hello-world.json --pidfile &References :

