Why Mock APIs?
As a mobile / front-end developer, have you ever been blocked due to a dependency on a back-end API that is not readily available for consumption? This scenario is very frequent in fast paced development teams where multiple front-end and back-end teams work in parallel whilst striving to resolve dependencies with one another.
What is Mockatron
I am building Mockatron - a simple tool that helps front-end / mobile developers setup a quick Mock HTTP API Server without having to wait for actual back-end APIs to be available.
The goal is to help parallelize development efforts of teams so that once the actual APIs are available, consumers can simply switch to the original implementation without having to make a whole lot of changes.
By defining a simple set of configuration rules, you can stand up a portable mock HTTP server that can return **static **as well as **dynamic **response data. Some of the features that are offered today include:
Handlebars style templates to support dynamic generation of responses from object templates.
Proxy support to mock specific routes and redirect others to your actual back-end server.
Support for static responses (e.g plain old JSON data).
Example
Let us assume that we are going to build a mobile / web application like Amazon that will list products from a back-end API. Our goal is to use Mockatron and setup a mock API server that will return mock products each time a call to /api/products
is called from the front-end / mobile app.
Install Mockatron
Pre-requisities:
Make sure that you have Node and npm installed.
$ npm install -g mockatron
Setup your mock definitions
Create an empty folder for us to configure our mock server. Give it an arbitrary name (e.g products).
Inside the products
folder, create a configuration folder named config
. We will be configuring our mock end points and constraints (more on this later) in this folder.
Inside the config
folder, create a main.json file. Here is my folder structure that I have created
products
└── config
├── main.json
Let us start by writing a simple config in our main.json
file.
{
"contextPath": "/api",
"routes": [
{
"path": "/products",
"method": "GET",
"responses": [
{
"body": "'no results found'",
"statusCode": 200
}
]
}
]
}
The above configuration should be straightforward. The contextPath
key specifies where the defined routes will be mounted while running our mock server. For e.g if we are hosting our mock server at localhost:8080
, http://localhost:8080/api/products
would match the /products
route that we have defined above.
The responses
array specifies the different response bodies that this route can potentially return. In this case, we are going to return a simple string body with a status code of 200.
Generate a mock server
Now that we have setup our configuration, go ahead and run the below command inside the products
directory.
$ mockatron --config config --out output
If everything went well without errors, an output directory will be created in the specified path.
Run the mock server
-
cd
into theoutput
path -
$ npm i
- This will install all dependencies for the mock server -
$ npm start
- This will run a mock server on port8080
Ideally, you should see the following messages in stdout
> mockatron_server@1.0.0 start
> node index.js
Started application on port 8080
Open a tab in your browser or send a curl
request to http://localhost:8080/api/products
and you should get no results found
as the response with a status code of 200.
And that's it! We have a mock API server up and running in less than 10 lines of code.
Dynamic responses
The above config returns the same static text every time we hit /api/products. However, most of the time while building a front-end or mobile app, we would need to generate variability in our responses to simulate a real world scenario. To achieve this go back to the configuration folder and create another file named products.json
products
└── config
├── main.json
├── products.json
Inside the products.json
file, we will try to create a template for our response.
{
"productList": [
{{#array 5 20}}
{
"price": "{{float 50.0 5000.0}}",
"rating": {{int 1 5}},
"id": {{@index}},
"sku": "{{uuid}}",
"name": "{{word 2}}"
}
{{/array}}
]
}
We will use the productList
object to return an array of products based on the template that we have defined above.
The #array
helper generates a random number of objects (between 5 & 20) that it encompasses. The array is closed out with the {{/array}}
tag.
Each object in the array can again be randomized. The float
and int
helpers take min and max arguments and generate a random number in between. Similarly there are helpers for generating a random uuid
, random text using the word
helper and so on. A complete list of response helpers can be found here.
To hook up the productList
definition array to our /api/products
route, head back to our main.json
file and modify the existing config like so.
{
"contextPath": "/api",
"routes": [
{
"path": "/products",
"method": "GET",
"responses": [
{
"body": "{{{def 'products' 'productList'}}}",
"statusCode": 200
}
]
}
]
}
We have only changed the body
to load the productList
definition that we defined in the products.json
file instead of static text. For this, we use the {{{def <json-file-name> <definition>
.
Now, lets rebuild our mock server. Go back to the root folder (the products
directory) and run the following command
$ mockatron --config config --out output
$ cd output
$ npm i && npm start
Now open a browser and try hitting http://localhost:8080/api/products
multiple times. You will see that you get a different response each time!
Adding Constraints to Routes
Now that we are able to generate dynamic response data, let us look at adding constraints that determine when the response should be generated.
Constraints are synonymous to if (condition) then return response
in Mockatron. Think of a constraint as logical blocks that we will put inside an if
block.
Constraints can be added to each element of the responses
key inside a route
.
As an example, assume that the products API that we are building mandates a search
query parameter in the URL and we should return a response only if the search
query parameter is not null.
Let's head back to our main.json
file and add the above rule as a constraint.
{
"contextPath": "/api",
"routes": [
{
"path": "/products",
"method": "GET",
"responses": [
{
"constraint": "{{neq (query 'search') undefined}}",
"body": "{{{def 'products' 'productList'}}}",
"statusCode": 200
},
{
"body": "'No Results found'",
"statusCode": 200
}
]
}
]
}
We see that the responses
array now contains 2 elements. The first element contains a constraint
key that evaluates if the incoming request's query parameter - search !== undefined
. Only if this condition is satisfied, will the body get executed.
Otherwise, Mockatron will fall back to the next object in the responses
array which basically returns a static string like before.
Remember: Contents in the responses
array are evaluated in the order in which they are listed in the configuration
A full list of Mockatron constraint helpers are available here .
Nesting Constraints
We can nest multiple constraints into a single constraint. For e.g what if we want to return the response only if the search query param is not null and the price param > 0?
Modify the constraint
section like so.
"constraint": "{{and
(neq (query 'search') undefined)
(gt (query 'price') 0)
}}",
Static Response Data
Sometimes we may not want to deal with templating or needing dynamic response data for all our routes and a simple plain JSON object would be sufficient. To return static JSON content as response, use the {{file <json_file_name>}}
helper instead of the {{def}}
helper that we were using till now.
Let's assume that we add a new route called /static-product
to our list of routes. In main.json, lets add the following configuration to the existing routes
array.
{
"path": "/static-product",
"method": "GET",
"responses": [
{
"statusCode": 200,
"body": "{{file 'static-products'}}"
}
]
}
All you need is a valid JSON response defined in a file named static-products.json
in the config
folder alongside the main.json
file.
What's next
Try adding multiple routes, responses and constraints and see what works for you. If you want to report a bug / discuss a new feature request, reach out to me / raise an issue in Github.
In the next post, I will talk about configuring proxy support in Mockatron. This could be really powerful if you want to use Mockatron as a proxy server that will mock only specific routes while proxying other requests to your actual back-end APIs. Stay tuned!
Top comments (0)