mountebank

mountebank - over the wire test doubles

Fork me on GitHub

the apothecary

Stubs

Stubs are a type of test double that return a canned response based on the request. mountebank allows you to define a list of stubs when creating an imposter, but does not support changing stubs once the imposter has been created.

mountebank associates both a list of responses and a list of predicates with each stub. Since mountebank knows that setting up stubs can sometimes be difficult, he can optionally add a matches element each time the stub is used if you start mb with the --debug command line flag. You can retrieve the matches by issuing a GET or DELETE to the imposter.

It doesn't make sense to create an empty array of responses, but each response is under no obligation to override the defaults (every protocol defines a default for every response field; see the protocol-specific documentation pages linked to from the sidebar for more details). The responses array defines a circular buffer - every time the stub is used for the request, the first response is pulled from the front of the responses array, evaluated, and pushed to the back of the array. This elegantly does what you want. In the common case, when you always want to return the same response, you just add one response to the array. More complex scenarios will require that the same endpoint returns a sequence of different responses for the same predicates. Simply add them all to the array in order. When the sequence finishes, it will start over. More complexity can be added by simply adding more responses to the array without complicating the contract.

Response Types

Each stub response is defined by a specific response type that defines the behavior of the response. The response types currently supported are:

Response Type Description
is Merges the specified response fields with the response defaults (see the protocol page linked to from the sidebar on the left for the defaults).
proxy Proxies the request to the specified destination and returns the response. The response is saved and can be replayed on subsequent calls.
inject Allows you to inject a JavaScript function to create the response object.

See the proxy page and the injection page for detailed examples of those response types, and the predicates page for examples of stubs with predicates. Multiple stubs only make sense with predicates.

Stubs can be decorated by adding a _behaviors object. See the behaviors page for more details.

Example

Let's create an http imposter with a couple of is response types. It will simulate a RESTful endpoint that creates a customer the first time it's called and returns a 400 the second time it's called because the email already exists. We add a second stub that returns a 404 on any request that isn't a POST to /customers/123


POST /imposters HTTP/1.1
Host: localhost:14156
Accept: application/json
Content-Type: application/json

{
  "port": 4545,
  "protocol": "http",
  "stubs": [
    {
      "predicates": [
        {
          "equals": {
            "method": "POST",
            "path": "/customers/123"
          }
        }
      ],
      "responses": [
        {
          "is": {
            "statusCode": 201,
            "headers": {
              "Location": "http://localhost:4545/customers/123",
              "Content-Type": "application/xml"
            },
            "body": "<customer><email>customer@test.com</email></customer>"
          }
        },
        {
          "is": {
            "statusCode": 400,
            "headers": {
              "Content-Type": "application/xml"
            },
            "body": "<error>email already exists</error>"
          }
        }
      ]
    },
    {
      "responses": [
        {
          "is": {
            "statusCode": 404
          }
        }
      ]
    }
  ]
}

Let's assume the application under test makes the initial call...


POST /customers/123 HTTP/1.1
Host: localhost:4545
Accept: application/xml
Content-Type: application/xml

<customer>
  <email>customer@test.com</email>
</customer>

HTTP/1.1 201 Created
Location: http://localhost:4545/customers/123
Content-Type: application/xml
Connection: close
Date: Thu, 09 Jan 2014 02:30:31 GMT
Transfer-Encoding: chunked

<customer><email>customer@test.com</email></customer>

...and the second call:


POST /customers/123 HTTP/1.1
Host: localhost:4545
Accept: application/xml
Content-Type: application/xml

<customer>
  <email>customer@test.com</email>
</customer>

HTTP/1.1 400 Bad Request
Content-Type: application/xml
Connection: close
Date: Thu, 09 Jan 2014 02:30:31 GMT
Transfer-Encoding: chunked

<error>email already exists</error>

Now let's see the matches elements that mountebank has saved for you:


GET /imposters/4545 HTTP/1.1
Host: localhost:14156
Accept: application/json

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Vary: Accept
Content-Type: application/json; charset=utf-8
Content-Length: 3721
Date: Thu, 09 Jan 2014 02:30:31 GMT
Connection: keep-alive

{
  "protocol": "http",
  "port": 4545,
  "numberOfRequests": 2,
  "requests": [
    {
      "method": "POST",
      "path": "/customers/123",
      "query": {},
      "headers": {
        "Accept": "application/xml",
        "Host": "localhost:4545",
        "Content-Type": "application/xml",
        "Connection": "keep-alive",
        "Transfer-Encoding": "chunked"
      },
      "body": "<customer>\n  <email>customer@test.com</email>\n</customer>",
      "timestamp": "2014-01-09T02:30:31.022Z"
    },
    {
      "requestFrom": "::ffff:127.0.0.1:60523",
      "method": "POST",
      "path": "/customers/123",
      "query": {},
      "headers": {
        "Accept": "application/xml",
        "Host": "localhost:4545",
        "Content-Type": "application/xml",
        "Connection": "keep-alive",
        "Transfer-Encoding": "chunked"
      },
      "body": "<customer>\n  <email>customer@test.com</email>\n</customer>",
      "timestamp": "2014-01-09T02:30:31.043Z"
    }
  ],
  "stubs": [
    {
      "predicates": [
        {
          "equals": {
            "method": "POST",
            "path": "/customers/123"
          }
        }
      ],
      "responses": [
        {
          "is": {
            "statusCode": 201,
            "headers": {
              "Location": "http://localhost:4545/customers/123",
              "Content-Type": "application/xml"
            },
            "body": "<customer><email>customer@test.com</email></customer>"
          }
        },
        {
          "is": {
            "statusCode": 400,
            "headers": {
              "Content-Type": "application/xml"
            },
            "body": "<error>email already exists</error>"
          }
        }
      ],
      "matches": [
        {
          "timestamp": "2014-01-06T04:31:44.584Z",
          "request": {
            "requestFrom": "::ffff:127.0.0.1:60524",
            "method": "POST",
            "path": "/customers/123",
            "query": {},
            "headers": {
              "Accept": "application/xml",
              "Host": "localhost:4545",
              "Content-Type": "application/xml",
              "Connection": "keep-alive",
              "Transfer-Encoding": "chunked"
            },
            "body": "<customer>\n  <email>customer@test.com</email>\n</customer>"
          },
          "response": {
            "statusCode": 201,
            "headers": {
              "Location": "http://localhost:4545/customers/123",
              "Content-Type": "application/xml",
              "Connection": "close"
            },
            "body": "<customer><email>customer@test.com</email></customer>",
            "_mode": "text"
          }
        },
        {
          "timestamp": "2014-01-06T04:31:44.587Z",
          "request": {
            "requestFrom": "::ffff:127.0.0.1:60523",
            "method": "POST",
            "path": "/customers/123",
            "query": {},
            "headers": {
              "Accept": "application/xml",
              "Host": "localhost:4545",
              "Content-Type": "application/xml",
              "Connection": "keep-alive",
              "Transfer-Encoding": "chunked"
            },
            "body": "<customer>\n  <email>customer@test.com</email>\n</customer>"
          },
          "response": {
            "statusCode": 400,
            "headers": {
              "Content-Type": "application/xml",
              "Connection": "close"
            },
            "body": "<error>email already exists</error>",
            "_mode": "text"
          }
        }
      ]
    },
    {
      "responses": [
        {
          "is": {
            "statusCode": 404
          }
        }
      ]
    }
  ],
  "_links": {
    "self": {
      "href": "http://localhost:14156/imposters/4545"
    }
  }
}