Overview

Mock Server powered by Mountebank and Docker

No Comments

Abstract

When building applications which are dependent on other systems e.g. for business logic or data integration purposes the question of testing without these systems arrises. In a classic approach we usually mock them out on a unit test or functional test basis. But still something can break on the way through the application stack down to the network layer.

In this article we focus on a solution which provides a mock server for such a test which is performed through the stack. This way calls through an application run against a mock server and can be tested thoroughly. This allows thorough application and system end-to-end tests.

Mountebank

Mountebank is an open source tool, which provides cross-platform, multi-protocol test doubles on a network [1]. An application which is supposed to be tested, just needs to point to the IP or URL of a Mountebank instance instead of the real dependency. It allows to test your application through the whole application stack, as you would with traditional stubs and mocks. Supported protocols include HTTP, HTTPS, TCP and SMTP.

For this puprose it provides a DSL which can be used to configure imposter stubs to provide static or dynamic responses on requests [2]. These stubs’ functionality is extended by a proxy mode to record and replay calls to the original system [3] and a mock verification system, which allows the querying for responses of asynchronous calls[4].

For most cases a simple request response mock stub is enough. Such a definition for example for a HTTP mock would be defined as followed:

{
  "port": 8010,
  "protocol": "http",
  "name": "My Mock",
  "mode": "text",
  "stubs": [
    {
      "responses": [
        {
          headers: {
            'Content-Type': 'text/xml'
          },
          body: "..."
        }
      ],
      "predicates": [
        {
          "and": [
            {
              "equals": {
                "path": "/MyService/MyOperation"
              }
            },
            {
              "contains": {
                "body": "Mountebank"
              }
            }
          ]
        }
      ]
    }
  ]
}

It will listen on port 8010 for an HTTP call. When the predicates are met, in this cases a call to /MyService/MyOperation which contains “Mountebank” in the POST body, an HTTP response will be sent with the HTTP Content-Type “text/xml” and the body “…”.

This definition can be provided to Mountebank either by the web UI, which is accessible from port 2525, via the REST API or when the application is started.

Mock Data File Structure

When the application is started a configuration file can be sent automatically to the Mountebank instance via its REST API.

Because such a file with multiple stubs can be huge and complicated a simplification is necessary. This is provided the by integration of JavaScript EJS templating. It can be used to construct a larger mock data set from multiple files in different folders.

The root file “imposters.ejs” importing a list of mock sub folders for such a large mock data set would look like that:

{
  "port": 8010,
  "protocol": "http",
  "name": "NKD Mock",
  "mode": "text",
  "stubs": [
    <% include myServiceA/imposters.ejs %>,
    ...
  ]
}

Here the subfolder file “myServiceA/imposters.ejs” would specify the stubs and the responses, which could be in separate files for complexity reduction reasons:

{
  "responses": [
    {
      "inject": "<%- stringify(filename, 'myServiceA/responseA.ejs') %>"
    }
  ],
  "predicates": [
    {
      "and": [
        {
          "equals": {
            "path": "/MyService/MyOperation"
          }
        },
        {
          "contains": {
            "body": "Mountebank"
          }
        }
      ]
    }
  ]
},
{
  "responses": [
    {
      "inject": "<%- stringify(filename, 'myServiceA/default.ejs') %>"
    }
  ],
  "predicates": [
    {
      "equals": {
        "path": "/MyService/MyOperation"
      }
    }
  ]
}

In this case we have a additional default fallback when the previous predicates are not evaluated successfully.

The response itself is returned as a Json object from a JavaScript function from the response file “myServiceA/default.ejs”:

function() {
  return {
    headers: {
    'Content-Type': 'text/xml'
  },
    body: "..."
  };
}

Docker

Docker is a Open Source technology which allows the virtualisation of maschines as isolated containers on the host system. It provides the fast and resource efficient creation of VMs by using Linux technologies such as Cgroups and Namespaces. This enables the creation of portable, reproducible and immutable infrastructures. These are a huge bonus for the creation, reproducible execution of test scenarios which include infrastructure.

Building the Mock Server

In order to build a Docker image which is pre-filled with our mock data for our mock server, we use a Docker image which has Mountebank pre-installed and is available from the Docker repository. Our Dockerfile looks like this:

FROM                    cpoepke/mountebank-basis:latest
 
ADD resources/imposters /mb/
RUN ln -s /usr/bin/nodejs /usr/bin/node
 
EXPOSE 2525
EXPOSE 8010
 
CMD mb --configfile /mb/imposters.ejs --allowInjection

When building the Docker image, it copies our mock data from the “resources/imposters” folder into “/mb” in our new Docker image, exposes our Mountebank ports and provides a run command which loads the mock data when the container is started.

Building the Docker image is as usual:

build --tag="my-ws-mock" .

Running the Mock Server

Now to run our mock server with already mapped ports, we just need this command:

run -t -i -p 2525:2525 -p 8010:8010 --name='my-ws-mock' my-ws-mock

Notice: Do not to forget the port forwarding on Mac and Windows for boot2docker!

Further Work

Further work can be done by integrating the creation of this docker image and its deployment to a repository using a continuous integration/continuous deployment pipeline. From there it can be used in automated integration, acceptance, smoke or system end-to-end tests depending on your testing strategy. Even larger test scenarios are possible, where multiple different mock servers can be defined to test a distributed systems such as a Microservices based architecture by starting them when the infrastructure is initialized.

Conclusion

We have shown in this post how an external mock server with a large mock data base can be defined. This mock server can be packed by using the container technology Docker and can be further more integrated in the continuous integration/continuous deployment pipeline. This provides us with the possibility to test the whole application stack through to the network layer.

References

[1] http://www.mbtest.org
[2] http://www.mbtest.org/docs/api/stubs
[3] http://www.mulesoft.org/documentation/display/current/Unit+Testing
[4] http://www.mbtest.org/docs/api/mocks
[5] https://www.docker.com/whatisdocker/

Comment

Your email address will not be published. Required fields are marked *