slothi-working

One day Slothi got an assigment to write a client that would interface with an HTTP server.


He was given a list of API definitions and a series of requirements that his client needed to perform when calling the HTTP server. However, the HTTP server was not ready for integration and would be implemented probably months after the client.


Slothi was now facing a dilemma!


How could he test the client functionality without the server side?

Mocking

Mocking is a technique used to replicate a system’s or component’s behavior during testing or development. To imitate the behavior of a system or component, it entails developing a simulated or fake version of it.


As a result, developers may test their code without having to rely on these other services, which might be unavailable or faulty when they are being tested.

Building an HTTP Mock Server

Slothi decided to mock the HTTP server after founding out about the concept of mocking.


He set down, took a note and started writing down the tasks he needed to do:

  1. Pick a programming language.
  2. Craft the project structure.
  3. Implement the Mock Server.
Picking the programming language

Slothi wanted to quickly build this project and thought the best thing was to use Python. He selected Python because of it’s simplicity, versatility, cross-platform compatibility, extensive libraries and frameworks.

Crafting the project structure

Slothi took the whiteboard and started drawing the project structure he wanted to build for the mock server.


project-name/
├── samples/
│   └── mock_endpoints.json
├── src/
│   ├── app.py
│   └── ...
├── setup.py
├── README.md
└── requirements.txt


  • project_name
    The root package containing the actual Python modules for the project.

  • samples
    A package that contains mock definition samples

  • src
    A package containing the actual Python code for the project. (i.e., files that end with .py).

  • setup.py
    A Python script that provides metadata about the project (e.g., its name, version, author, license, and dependencies) and allows for installation of the project via pip.

  • README.md
    A Markdown file containing information about the project, such as installation instructions, usage examples, and a brief description of the project’s purpose.

  • requirements.txt
    A file containing a list of required Python packages for the project. This file can be used to install dependencies via pip install -r requirements.txt.

Implementing the Mock Server

The first thing Slothi did was to find a name for his project.


He decided to call the project Loki, because he is the North god of mischief and trickery and sounded like a fitting name for a mocking server.


After finding the name he crated the setup.py

import setuptools

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
     name="loki",
     version="1.0.0",
     author="The Sloth Dev",
     author_email="slothi@thesloth.dev",
     description="Loki is an HTTP mocking server that allows user to define API definitions using json files.",
     long_description=long_description,
     long_description_content_type="text/markdown",
     url="https://github.com/the-sloth-dev/loki",
     packages=setuptools.find_packages(),
     classifiers=[
          "Programming Language :: Python :: 3",
          "License :: OSI Approved :: Apache Software License",
          "Operating System :: OS Independent",
     ],
     python_requires='>=3.6',
     license="Apache License 2.0",
)

Slothi wanted the functionality to follow a certain logic that would minimize the effort needed for mocking a different set of endpoints.


His idea was to have the server load mock definitions from a JSON file at runtime and use that to dynamically build all the needed endpoints.


He thought a bit and came up with the following structure:

{
    "method": "GET",
    "path": "/do/something",
    "status_code": 200,
    "headers": {
    },
    "response_body": {
    }
}
  • method
    This field specifies the HTTP method that needs to be mocked for the request, in this case “GET”. Other supported HTTP methods will include “POST”, “PUT”, “DELETE”, and “PATCH”.

  • path
    This field specifies the path or endpoint of the resource that will be mocked. In this example, the path is “/do/something”.

  • status_code
    This field specifies the HTTP status code that will be returned by the server, in this case 200. HTTP status codes are three-digit numbers that indicate the success or failure of a request. The server will mock all needed status codes, 200 (OK), 404 (Not Found), and 500 (Internal Server Error).

  • headers
    This field contains any HTTP headers that should be returned by the server. Supporting mocking for HTTP headers will allow the server to provide additional information about the response, such as the content type, encoding, and caching instructions.

  • response_body
    This field contains the body of the HTTP response that the server should mock. Depending on the needs, the server will be able to mock a response body that may contain data, error messages, or other information.


Now Slothi needed to figure out a couple of key point in regard to his implementation.

  1. How to dynamically load the JSON file that contains the mock definitions?
  2. How to dynamically generate endpoints from the json mock definitions?


To load the mock definitions from JSON files Slothi decided to use an environment variable because it provides a flexible way to specify the location of files without hardcoding paths in the code.

import json
import os

def load_config_file(config_file):
    with open(config_file) as file:
        return json.load(file)

def main():
    mock_endpoints = load_config_file(os.environ.get('MOCK_ENDPOINTS_JSON'))

if __name__ == '__main__':
    try:
       main()
    except KeyboardInterrupt:
       sys.exit(0)

To dynamically generate endpoints Slothi decided to use Flask.

Flask: Python web framework used to build HTTP servers.

He updated the requirements.txt to specify the Flask dependency

Flask==2.0.2

Then he installed the dependency

pip install -r requirements.txt

And updated the code to dynamically generate endpoints from the loaded JSON file with mock definitions

import json
import os
import sys
from flask import Flask, jsonify


def create_endpoint(path, method, response_body, status_code=200, headers=None):
     def endpoint():
         return jsonify(response_body), status_code, headers
     endpoint.__name__ = f"{method}_{path.replace('/', '_')}"
     return endpoint


def register_mock_endpoints(app, mock_endpoints):
     for endpoint in mock_endpoints:
         method = endpoint['method']
         path = endpoint['path']
         status_code = endpoint.get('status_code', 200)
         headers = endpoint.get('headers', {})
         response_body = endpoint['response_body']

         endpoint_func = create_endpoint(path, method, response_body, status_code=status_code, headers=headers)
         app.add_url_rule(path, view_func=endpoint_func, methods=[method])


def load_config_file(config_file):
     with open(config_file) as file:
     return json.load(file)


def main():
     mock_endpoints = load_config_file(os.environ.get('MOCK_ENDPOINTS_JSON'))
     app = Flask(__name__)

     register_mock_endpoints(app, mock_endpoints)
     app.run(debug=os.environ.get('DEBUG'))


if __name__ == '__main__':
     try:
        main()
     except KeyboardInterrupt:
        sys.exit(0)

Now Slothi is ready to run his mocking server and test the implementation.


He crates a sample of mock_endpoints.json.

[
     {
          "method": "GET",
          "path": "/api/v1/users",
          "status_code": 200,
          "headers": {
              "Content-Type": "application/json"
          },
          "response_body": {
              "users": [
                  {
                      "id": 1,
                      "name": "John Doe",
                      "email": "john.doe@example.com"
                  },
                  {
                      "id": 2,
                      "name": "Jane Doe",
                      "email": "jane.doe@example.com"
                  }
              ]
          }
     },
     {
          "method": "POST",
          "path": "/api/v1/login",
          "status_code": 200,
          "headers": {
               "Content-Type": "application/json"
          },
          "response_body": {
               "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
          }
     }
]

Declares the enviorment variable.

export MOCK_ENDPOINTS_JSON=/tmp/mock_endpoints.json

Runs the mock server.

python3 app.py
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
* Serving Flask app 'main' (lazy loading)
* Environment: production
  WARNING: This is a development server. Do not use it in a production deployment.
  Use a production WSGI server instead.
* Debug mode: off

Slothi is happy looks like the server is running and he should be ready to test the functionality.

He uses curl to verify the endpoints that have been mocked.

$ curl -i -w "\n" -H "Content-Type: application/json" -H "Accept: application/json" -X POST http://localhost:5000/api/v1/login
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.9.2
Date: Sun, 26 Feb 2023 17:31:00 GMT
Content-Type: application/json
Content-Length: 49
Connection: close

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"}

$ curl -i -w "\n" -H "Content-Type: application/json" -H "Accept: application/json" -X GET http://localhost:5000/api/v1/users
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.9.2
Date: Sun, 26 Feb 2023 17:31:51 GMT
Content-Type: application/json
Content-Length: 128
Connection: close

{"users":[{"email":"john.doe@example.com","id":1,"name":"John Doe"},{"email":"jane.doe@example.com","id":2,"name":"Jane Doe"}]}

Slothi jumps from joy, his mocking server works, now he can generate JSON mocking definitions with the API definitions.


HTTP Mock Server Source Code