When backend and frontend developers need a controllable response from the services they depend on, they use a service mock tool. In microservice environments, using service mocks allows reducing resource requirements and generally speeds up the development process.

Mockintosh is an Open Source service mock technology that is designed specifically for mocking in microservice environments. It offers a number of benefits. These include a small resource footprint, multi-service mocking and resiliency testing features.

In this article, we will learn how to install and run the Mockintosh tool for simulating multiple microservices from a single laptop. Towards the end of this article, we'll investigate the options for configuring clients to use a mock instead of a real service.

How dependencies are mocked:

image2

Installing Mockintosh

There are two main ways of installing Mockintosh: via the Python package manager and via Docker. The bare Python way allows for a simpler command-line, while Docker provides perfect isolation and is a step towards running mocks inside Kubernetes.

When installing Mockintosh as a Python package, you need Python 3.x+ as a prerequisite, with the standard pip package manager. The installation command is as simple as:

Copy to clipboard
pip install mockintosh

If you want to start using the Docker way, the prerequisite is to install Docker, and then pull the latest Mockintosh image:

Copy to clipboard
docker pull testrio/mockintosh:latest

Mockintosh aims for the smallest Docker image size, so the pull process should complete super-fast.

After installation, you can quickly validate it with a simple help command:

Copy to clipboard
mockintosh --help

for the Python installation.

Copy to clipboard
docker run testrio/mockintosh --help for the Docker variant.

That command will print out a short help message like this:

Copy to clipboard

usage: mockintosh [-h] [-q] [-v] [-i INTERCEPTOR [INTERCEPTOR ...]]
                  [-l LOGFILE] [-b BIND]
                  [source ...]
    
positional arguments:
source              Path to configuration file and (optional) a list of the service names
                    to specify the services to be listened.
    
optional arguments:
-h, --help              show this help message and exit
-q, --quiet             Less logging messages, only warnings and errors
-v, --verbose           More logging messages, including debug
-i INTERCEPTOR [INTERCEPTOR ...], --interceptor INTERCEPTOR [INTERCEPTOR ...]
                        A list of interceptors to be called in \<package\>.\<module\>.\<function\> format
-l LOGFILE, --logfile LOGFILE
                        Also write log into a file
-b BIND, --bind BIND    Address to specify the network interface

Now, we will learn how to run Mockintosh, in both the Docker and Python variants.

Running the Mock Server with Mockintosh

The main input for the Mockintosh mock server is a configuration file, which, when starting out, can be a very simple one. The format for the Mockintosh config file can be YAML or JSON. The former is easier for humans, the latter is more suitable for machines. For visual simplicity, we will use YAML examples. Let's start with a simple one:

Copy to clipboard

services:
- name: First Test
  port: 8001
  endpoints:
  - path: /ping
    response: Pong

This file is saved as "simple.yaml" into some directory. Let's understand it line by line.

  • The first line with "---" is the YAML "document start" marker. It is optional, but is a good visual marker for YAML documents.
  • The "services:" line ends with colon, which means it's a key with some nested content.
  • The fact that the next line starts with "- " means the content is a list of items, one item in our case.
  • The "name: First Test" is an optional name for the API we serve. It provides important context for anyone reading our configuration.
  • The "port: 8001" setting is the only required setting and it sets a TCP port number, where the API will be served and accessed.
  • Now the "endpoints:" section starts, it's a list of items that contains definitions of URLs that the mock server will recognize. Endpoint definitions are the most important part of the Mockintosh configuration. In practice, you will spend most of your time configuring those.
  • Our configuration currently contains only a single endpoint with the expected "path: /ping" and the simplistic response template "Pong".

This is a good start. Now, let's learn to run this configuration, both in the Python and Docker installation variants.

For the Python installation variant, starting the server is as easy as this:

Copy to clipboard

mockintosh simple.yaml

With the Docker, the command-line becomes a bit more complex:

Copy to clipboard

docker run -it -p 8001:8001 -v `pwd`:/mck testrio/mockintosh /mck/simple.yaml

This is due to the need to publish container ports with "-p 8001:8001" and make the folder with the config accessible inside the container with "-v pwd:/mck".

When the server starts, it will print out some log messages that confirm it's functioning:

Copy to clipboard

[2021-02-21 08:31:02,996 root INFO] Mockintosh v0.7 is starting...
[2021-02-21 08:31:02,996 root INFO] Reading configuration file from path: /tmp/1/simple.yaml
[2021-02-21 08:31:02,997 root INFO] Configuration file is a valid YAML file.
[2021-02-21 08:31:03,002 root INFO] Configuration file is valid according to the JSON schema.
[2021-02-21 08:31:03,007 root INFO] Serving at http://localhost:8001 the mock for 'First Test'
[2021-02-21 08:31:03,007 root INFO] Mock server is ready!

When we see that it's ready, we can start querying our mock, either via browser, or with a cURL command:

Copy to clipboard

ubuntu:~$ curl http://localhost:8001/ping
Pong

If you will open the same URL in your web browser, you will see the same "Pong" message. Additionally, you can see the processed requests in the console log of Mockintosh:

Copy to clipboard

[2021-02-21 09:35:31,462 tornado.access INFO] 200 GET /ping (172.17.0.1) 1.75ms
[2021-02-21 09:35:59,756 tornado.access INFO] 200 GET /ping (172.17.0.1) 1.60ms

Now it's time to press Ctrl + C in the window where you started Mockintosh to stop the server.

A More Complex Example

Let's add some more endpoints to see how can we enrich the mock functionality:

Copy to clipboard

services:
- name: First Test
  port: 8001
  endpoints:
  - path: /ping
    response: Pong
  - path: /api/collection/{{id}}
    method: POST
    response:
      status: 202
      body: '{"objId": {{id}}, "created": "{{date.date}}"}'
      headers:
        Content-Type: application/json

As you can see, we have added a second endpoint to the list, with some interesting new configuration features. The first thing to notice is that the "path" option now contains a special value "{{id}}" inside. This means the corresponding part of the URL can have any value, and that value will be stored into the variable named "id". We have also specified that the only HTTP method we will match to is POST.

In the response section, we used the extended definition, which has a specification for the HTTP status code, a response header for "content-type", and a dynamically generated JSON response body. In that body, we used the "{{id}}" variable as a value for the "objId" key in the JSON response. We have also used the dynamic value of "{{date.date}}" to insert the current date.

Let's start Mockintosh again with this configuration file and query it a couple of times:

Copy to clipboard

$ curl -X POST http://localhost:8001/api/collection/1234
{"objId": 1234, "created": "2021-02-21T12:09:34.500003"}
    
$ curl -X POST http://localhost:8001/api/collection/265
{"objId": 265, "created": "2021-02-21T13:56:07.916301" }

You can see that "objID" is exactly what we have in the URL, and the "created" date is dynamic. The number of endpoints is unlimited, and the configuration can get pretty complex. In future blog posts, we will review in-depth capabilities of matching requests andtemplating responses, as well as some other features of Mockintosh.

Mocking Multiple Services at Once

One more important capability to try is serving multiple mocks at once from a single process/container and without spending excessive resources. Let's review the YAML item that is added to "services" list in the configuration file:

Copy to clipboard

services:
- name: First Test
  port: 8001
  endpoints:
  - path: /ping
    response: Pong
  - path: /api/collection/{{id}}
    method: POST
    response:
      status: 202
      body: '{"objId": {{id}}, "created": "{{date.date}}"}'
      headers:
        Content-Type: application/json
    
- name: Single Page App
  port: 8002
  endpoints:
  - path: /
    response: "@index.html"
  - path: /api/items
    response: "@items.json"

Make note of a different "port" number for the second service, since we want to access two services separately. We have also specified two endpoints, each referencing a file on disk with an "@"-prefixed value. In the HTML file, there is a single-page HTML application with JavaScript code that accesses the API. We mock one of those API calls by sending back the contents of the "items.json" file.

Restarting Mockintosh and experimenting with cURL shows that we have the second service served from port 8002:

Copy to clipboard

$ curl http://localhost:8002/api/items
{
    "items": [
        "first",
        "second",
        "third"
    ]
}

There can be any number of services inside a single configuration file. It is also possible to serve multiple services from the same TCP port, using the "virtual hosts" approach, quite typical for modern web servers.

Since Mockintosh is all configuration-driven, learning its config file syntax becomes the central part in learning the tool. There are many features in the Mockintosh configuration. If you want to learn more, please navigate to the documentation and some further blog posts.

Configuring Clients to Use the Mock

The final step in using the mock is to actually configure the desired client to use the mock's address instead of a real service. In the real world, one rarely queries a mock service directly. Usually a microservice or single-page application interacts with the dependency services. We just need to replace the heavy dependency with a lightweight mock.

There are several ways to achieve actual mock usage, depending on the use case and constraints. Let's consider the diagram of typical client and microservices connected to each other:

image3

Mocking is not required for the client of my service, nor it is needed for the service that I'm working on right now. It is only needed for the service I depend on, to turn that dependency into an obedient and manageable mock.

The easiest way to use the mock is to change the address of the "DEP" service in your configuration file on the "SUD" microservice side. This obviously works only when you have such a "backend address" configurable in your "SUD". Then you can deploy and start Mockintosh at any host and port, and point the "SUD" to it by editing the address property.

This also works for the "frontend development" case, when a single-page application of "SUD" depends on a backend API, and the developer wants to get predictable responses from the "DEP" backend. Instead of a time-consuming arrangement of "right and realistic DEP state", the developer just configures the SUD to talk to the Mockintosh mock. The latter is easily configured with desired responses and started locally, ensuring quick turnaround when changes are needed. Mockintosh will ensure CORS is satisfied, due to its inherent frontend developer friendliness.

image1

If you have no ability to change the "SUD-to-DEP" connection address, but have administrator access to the OS, you can use it another way. On the SUD machine, configure the mock's IP address to be used instead of the real "DEP" service address, via the /etc/hosts file. From the mock configuration, it additionally requires running the mock from the same port as the original service, with compatible SSL settings, and probably, with the correct "virtual host" name.

More advanced approaches involve drop-in replacement of Docker image and entrypoint, used in docker-compose or Kubernetes configuration files. For more information on these options, please refer to UP9's support channels.

To get started with Mockintosh, just install and start mocking. You can also gain observability into your microservices architecture with UP9. Sign up for free here.