Mocking Back End Services

Mocking Back End Services

First of all what is mocking? Mocking is creation of a replica or imitation of something. In simple language, mocking is creation of objects that simulate the behaviour of real objects. So why I would want to mock my back-end? Usually you will want to mock out your back-end services in order to run front-end end to end tests (e2e), such as Protractor in Angular. That allows your tests to run fast without dependancy on availability of real APIs. Other use case might be front-end and back-end teams decoupling. Both development teams agrees on expected APIs behaviour in the beginning of a project, then mocks are created and teams can work independently without blocking each other. Got your attention? Ok let’s see how to do it.

Setup

In this example we will build a mock for a lottery ticket validation service. Where ticket number can be passed as a query parameter and response object is created dynamically, but before we start make sure you have Node and NPM installed. You can check that by running commands below in a terminal:

command: node -v
output: v11.3.0

command: npm -v
output: 6.11.3

Then we need to create initial node project:

npm init
{
  "name": "mountebank-example",
  "version": "1.0.0",
  "description": "Example of how to create mocked back-end services using Mountebank",
  "main": "server.js",
  "scripts": {
    "start": "nodemon src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/codespacelab/mountebank-example.git"
  },
  "author": "Marius Jaraminas",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/codespacelab/mountebank-example/issues"
  },
  "homepage": "https://github.com/codespacelab/mountebank-example#readme",
}

Install Mountebank – mocking library, node-fetch – http library, nodemon – a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected:

npm install --save mountebank
npm install --save node-fetch
npm install --save-dev nodemon
{
  "name": "mountebank-example",
  "version": "1.0.0",
  "description": "Example of how to create mocked back-end services using Mountebank",
  "main": "server.js",
  "scripts": {
    "start": "nodemon src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/codespacelab/mountebank-example.git"
  },
  "author": "Marius Jaraminas",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/codespacelab/mountebank-example/issues"
  },
  "homepage": "https://github.com/codespacelab/mountebank-example#readme",
  "dependencies": {
    "mountebank": "^2.1.0",
    "node-fetch": "^2.6.0"
  },
  "devDependencies": {
    "nodemon": "^1.19.3"
  }
}

Create src directory and following files in it:

  • server.js
  • settings.js
  • mountebank-helper.js
  • lottery-service.js

For settings.js we just export an object with ports listed inside of it, its good practice to keep your configs separate from functionality.

module.exports = {
    port: 5000,
    lottery_service: 5001
};

Inside of mountebank-helper.js file we will setup Mountebank client which will be called while starting an app to add our lottery service mock. That needs to be done since by default Mountebank deletes all mocks every time it is restarted.

const fetch = require('node-fetch');
const settings = require('./settings');

const postImposter = (body) => {
    const url = `http://127.0.0.1:${settings.port}/imposters`;

    return fetch(url, {
        method: 'POST',
        headers: { 'Consent-Type': 'application/json' },
        body: JSON.stringify(body)
    });
}

module.exports = { postImposter };

After Montebank client is ready we need to create an actual service which we are mocking:

const mbHelper = require('./mountebank-helper');
const settings = require('./settings');

const addService = () => {
    const stubs = [{
        predicates: [{
            and: [
                {
                    exists: {
                        query: {
                            "lotteryTicket": true
                        }
                    }
                },
                {
                    equals: {
                        path: "/v1/validate",
                        method: "GET"
                    }
                }
            ]
        }],
        responses: [{
            is: {
                statusCode: 200,
                headers: {
                    "Consent-Type": "application/json"
                }
            },
            _behaviors: {
                decorate: `(config) => {
                    const ticketNumber = config.request.query['lotteryTicket'];
                    let isWinningTicket = false;
                    let prize = '20 euros';

                    if (ticketNumber) {
                        if (ticketNumber.match('50')) {
                            isWinningTicket = true;
                            prize = '50 euros';
                        } else if (ticketNumber.match('100')) {
                            isWinningTicket = true;
                            prize = '100 000 euros';
                        } else if (ticketNumber.match('car')) {
                            isWinningTicket = true;
                            prize = 'New car';
                        } else {
                            isWinningTicket = false;
                        }

                        config.response.body = { 
                            isWinningTicket: isWinningTicket,
                            prize: isWinningTicket ? prize : "Sorry try again"
                         };
                    } else {
                        config.response.body = { error: "Invalid lottery ticket" };
                        config.response.statusCode = 400;
                    }
                }`
            }
        }]
    }];

    const imposter = {
        port: settings.lottery_service,
        protocol: 'http',
        stubs: stubs
    };

    return mbHelper.postImposter(imposter);
}

module.exports = { addService };

Basically what this code does is:

  1. It creates stub for an endpoint: “/v1/validate?lotteryTicket=”
  2. Adds a decorator function. In which request query parameter is read, validated and response is setup according its value.
  3. Then imposter object is created and recorded into Mountebanks imposter endpoint using Mountebank client we created before.

Finally we need to setup our server.js file:

const mb = require('mountebank');
const settings = require('./settings');
const lotteryService = require('./lottery-service');

const mbServiceInstance = mb.create({
    port: settings.port,
    pidfile: '../mb.pid',
    logfile: '../mb.log',
    protofile: '../protofile.json',
    ipWhitelist: ['*'],
    allowInjection: true
});

mbServiceInstance.then(() => {
    lotteryService.addService();
});
  • Mountebank, settings and our lottery service are imported as constants.
  • New mountebank instance is created by calling mb.create and passing port and default settings to it.
  • Our service is registered on mountebank (doesn’t have to be one service).

Testing

To start our mocking service run npm start in a terminal (output should be similar to this, if it throws some errors make sure package.json file is the same as provided. Maybe you missed: “start”: “nodemon src/server.js” ?):

 npm start

> mountebank-example@1.0.0 start /Users/codespacelab/Documents/git/mountebank-example
> nodemon src/server.js

[nodemon] 1.19.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/server.js`
info: [mb:5000] mountebank v2.1.0 now taking orders - point your browser to http://localhost:5000/ for help
warn: [mb:5000] Running with --allowInjection set. See http://localhost:5000/docs/security for security info
info: [mb:5000] POST /imposters
info: [http:5001] Open for business...

After app start we can either use curl, postman or simple browser to test it. For simplicity we will use curl inside the terminal.

command: curl http://localhost:5001/v1/validate?lotteryTicket=1
output:
{
    "error": "Invalid lottery ticket"
}

command: curl http://localhost:5001/v1/validate?lotteryTicket=150
output:
{
    "isWinningTicket": true,
    "prize": "50 euros"
}

command: curl  curl http://localhost:5001/v1/validate?lotteryTicket=15dggddfggdcar
output:
{
    "isWinningTicket": true,
    "prize": "New car"
}

command: curl  curl http://localhost:5001/v1/validate?lotteryTicket=null
output:
{
    "isWinningTicket": false,
    "prize": "Sorry try again"
}

Everything seems to be working as expected. Full code can be found on Codespacelab Github. More details on Mountebank itself here.

Add Comment

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