Express: Creating a Stub Server for Visual Regression Testing
Continuing my previous post on Automated Visual Regression Testing, I will be discussing here a key aspect of our test architecture that allowed us to have consistent test data - the Stub Server.
Read my previous post here.
In this article, I will show you how easy it is to create your own Stub Server using the awesome express framework. So, grab a coffee (or tea or beer, whatever), sit back and enjoy the ride, I mean the tutorial.
Firstly, provided you have node already installed in your computer, create a project and proceed to install the following:
npm install --save-dev express
npm install --save-dev body-parser
npm install --save-dev cookie-parser
npm install --save-dev nodemon
- express - fast, unopinionated, minimalist web framework for node.
- body-parser and cookie-parser - parsers used to easily manipulate cookies and the request body
- nodemon - is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.
Next, create a folder named src and add the following files:
- server.js - this is going to be our entry point and will contain the nodemon configuration
- app.js - this file is where we will handle the endpoints requests
- route.js - this file is what allows us to route in which folder and what json file is to be served for a particular endpoint request
server.js
const nodemon = require('nodemon');
nodemon({
script: './src/app.js',
env: {
PORT: 4000
},
ext: 'js, json',
watch: ['./src', './public'],
ignore: ['server.js'],
delay: 1000,
});
nodemon
.on('start', () => {
console.log('Started API Server...');
})
.on('restart', (files) => {
console.log('Watcher has detected files have changed. API Server restarted...');
});
process.once('SIGINT', () => {
nodemon.once('exit', () => {
console.log('nodemon has cleanly exited');
process.exit();
});
});
app.js
const express = require('express');
const http = require('http');
const route = require('./route');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const app = express();
app.set('port', process.env.PORT || 4000);
app.use(bodyParser.json());
app.use(cookieParser());
const port = app.get('port');
app.all('/*', (req, res, next) => {
res.set('Content-Type', 'application/json');
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Access-Control-Allow-Headers', 'Content-Type,X-Requested-With,Authorization');
res.set('Access-Control-Allow-Credentials', 'true');
res.set('Access-Control-Allow-Methods', 'GET, POST, DELETE, PUT');
next();
});
// Custom Methods with Cookie Handling
app.get('/customEndpoint', (req, res) => {
let dir = path.join(__dirname, '/customEndpoint/');
route.getCustomData(req, res, dir);
});
// Pokemon Methods
app.get('*', (req, res) => {
let dir = path.join(__dirname, req.path + '/');
route.getDefaultData(req, res, dir);
});
const defaultResponse = (req, res) => {
let response = {
status: "OK",
message: `${req.method} successful!`
}
res.status(200).send(response);
}
app.post('*', defaultResponse);
app.put('*', defaultResponse);
app.delete('*', defaultResponse);
http.createServer(app).listen(port, () => {
console.log(`API Server listening on port ${port}`);
});
route.js
const fs = require('fs');
const path = require('path');
let loadJson = (res, dir, file = "success") => {
let fileName = path.join(dir, file + '.json');
let json = JSON.parse(fs.readFileSync(fileName, 'utf8'));
res.status(200).send(json);
};
exports.getCustomData = (req, res, dir) => {
let file = 'hasNoCookie';
const token = req.cookies.myCookie;
if (['hasCookie', 'hasNoCookie'].includes(token)) {
file = token;
}
loadJson(res, dir, file);
};
exports.getDefaultData = (req, res, dir) => {
loadJson(res, dir);
};
In your package.json, add a script to run the project
"scripts": {
"start": "node ./src/server.js"
}
Now you can run it using the command:
npm start
The stub server can now be accessed through localhost:4000.
Let's now take a closer look on how to use it. The easiest way to create your test endpoint data is taking advantage of the Pokemon methods:
app.get('*', (req, res) => {
let dir = path.join(__dirname, req.path + '/');
route.getDefaultData(req, res, dir);
});
The above code allows us to catch any GET requests that were not handled explicitly. By simply adding a success.json file in the folder path that is the same as the request path, the stub server will serve that file automatically.
For example, say you have a request endpoint localhost:4000/pokemonEndpoint. Under the src folder simply create a folder named pokemonEndpoint and add a success.json file with your data in it:
Now, whenever you hit localhost:4000/pokemonEndpoint, you will receive the data stored in the success.json file.
But what if you have a single endpoint but would like to serve different json data in different situations. Well, no problemo compadre! I have included an example of how to explicitly handle this situation:
# app.js
...
// Custom Methods with Cookie Handling
app.get('/customEndpoint', (req, res) => {
let dir = path.join(__dirname, '/customEndpoint/');
route.getCustomData(req, res, dir);
});
# route.js
...
exports.getCustomData = (req, res, dir) => {
let file = 'hasNoCookie';
const token = req.cookies.myCookie;
if (['hasCookie', 'hasNoCookie'].includes(token)) {
file = token;
}
loadJson(res, dir, file);
};
The above code shows you how you can pass cookies and use them to customise the path and file that you want the stub server to serve. In the above example, we have an endpoint at localhost:4000/customEndpoint and we would like to serve a different file depending on the presence of certain cookie value. So, we need to create a folder named customEndpoint and add the files using the cookie names:
Now when hitting the endpoint and passing the specific cookie value, you will be served with the configured json file accordingly. Sweet as!
Testing it in postman:
As an added bonus, since we are using nodemon, we don't need to restart the stub server whenever you add json files or edit the code, nodemon does it for us! Magic!
So there you have it folks, a quick and easy way to create a stub server which you can now use to mock your backend for your visual regression testing. Full source code can be found here.
Stay awesome, Gotham!
marcDacz
Have you considered adding a mode where api calls are proxied and cached / persisted locally for each unique call. Then you would not need to manually add data stubs. Just a suggestion 😊 Cheers!