This article enables one to quickly get started with writing simple Mocha test cases. It covers how to install Mocha and get up and running with it quickly.
To get started with the Mocha installation, you need to have node.js and npm installed on your system.
To install Mocha, run this from the terminal:
npm install –g mocha
This installs Mocha globally so that it can be run from anywhere.
This is the folder structure that I have set up:
Mocha-Test
test
package.json
test
config.js
test.js
We will go over the contents of these files. My package.json looks as below:
{
"name": "mocha_scripts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"chai": "^2.3.0",
"request": "^2.55.0",
"sinon": "^1.14.1",
"sinon-chai": "^2.7.0"
}
}
chai is a Behaviour Driven Development / Test Driven Development library for node and can be paired with any Javascript testing framework. Sinon is used to write standalone and test framework agnostic JavaScript test spies, stubs and mocks, while sinon-chai provides a set of custom assertions for using the sinon.js spy, stub and mocking framework with the chai assertion library and request is the standard npm module for making http and https calls.
From Mocha-Test/test, run “npm install”. This would pick the dependencies from package.json and install those dependencies. After running npm install, the directory structure would be as follows:
Mocha-Test
test
node_modules
chai
request
sinon
sinon-chai
package.json
test
config.js
test.js
Now that the dependencies are out of the way, we are ready to write the actual test cases. The test cases would go into the test.js file in the test sub-folder. The config.js file serves as a property or constants file that the test.js refers to. My config.js file looks like this:
module.exports = {
// service URLs
BASE_URL_OAUTH: ‘abc-test.apigee.net',
BASE_URL_LOCATION: ‘abc-test.apigee.net/v2/location',
PROTOCOL: 'https',
URL_WHITELIST: 'whitelist',
URL_TOKEN: 'v1/oauth/token',
URL_LOCATION: 'position',
URL_INVITATIONS: 'invitations',
URL_CALLBACK: 'callback',
CLIENT_ID: 'HaeVHIeWzjz6VlOFbciKb2iUX6U1rzyA',
CLIENT_SECRET: 'b3HPfUEUucPGxeom',
CLIENT_ID_BAD: 'HaeVHIeWzjz6VlOFbciKb2iUX6U1rzy',
SCOPE: 'LOCATION',
GRANT_TYPE_CC: 'client_credentials',
THROTTLE_DELAY: 1000
};
Assume that there are one or more API Proxies deployed in Apigee Edge with the following resources / conditional flows:
https://abc-test.apigee.net/v1/oauth/token
https://abc-test.apigee.net/v2/location/whitelist
https://abc-test.apigee.net/v2/location/position
https://abc-test.apigee.net/v2/location/invitations
https://abc-test.apigee.net/v2/location/callback
Also assume that there is a product in Edge containing the above proxies and that there is an app that contains this product with consumer key 'HaeVHIeWzjz6VlOFbciKb2iUX6U1rzyA' and consumer secret 'b3HPfUEUucPGxeom'.
Now we get to the actual test cases in test.js. The contents of test.js look like this:
var url = require('url'); var request = require('request'); var chai = require('chai'); var config = require('./config'); var expect = chai.expect; describe('Location API Test Suite', function() { var __msisdn = …; var __validation_code = …; var __X_MLM_Callback_Key = …; var __appkey = …; var __access_token; this.timeout(10000); before(function(done) { var urlObj = { protocol: config.PROTOCOL, hostname: config.BASE_URL_OAUTH, pathname: config.URL_TOKEN, }; var options = { url : url.format(urlObj), qs : { client_id : config.CLIENT_ID, client_secret : config.CLIENT_SECRET, grant_type: config.GRANT_TYPE_CC, scope : config.SCOPE }, followRedirect : false }; request.get(options, function (err, res, body) { expect(res.statusCode).to.equal(200); data = JSON.parse(res.body); __access_token = data['access_token']; expect(__access_token).to.not.be.empty; console.log("Access Token: " + __access_token); setTimeout(done, config.THROTTLE_DELAY); }); }); describe('Location API positive test cases', function() { it('should send invite to device', function(done) { var urlObj = { protocol: config.PROTOCOL, hostname: config.BASE_URL_LOCATION, pathname: config.URL_INVITATIONS, }; var postData = { msisdn: __msisdn }; var options = { url : url.format(urlObj), body: postData, json: true, headers : { Authorization : 'Bearer ' + __access_token }, followRedirect : false }; request.post(options, function(err, res, body) { console.log("Invitations Response: " + JSON.stringify(res.body)); expect(res.statusCode).to.equal(200); //data = JSON.parse(res.body); setTimeout(done, config.THROTTLE_DELAY); }); }); it('should resend invitation', function(done) { var urlObj = { protocol: config.PROTOCOL, hostname: config.BASE_URL_LOCATION, pathname: config.URL_INVITATIONS + '/' + __msisdn, }; var options = { url : url.format(urlObj), headers : { Authorization : 'Bearer ' + __access_token }, followRedirect : false }; request.put(options, function(err, res, body) { console.log("Resend invitation Response: " + res.body); expect(res.statusCode).to.equal(200); //data = JSON.parse(res.body); setTimeout(done, config.THROTTLE_DELAY); }); }); it('should get location', function(done) { var urlObj = { protocol: config.PROTOCOL, hostname: config.BASE_URL_LOCATION, pathname: config.URL_LOCATION + '/' + __msisdn, }; var options = { url : url.format(urlObj), qs : { type : 'live', max_loc_age : '3600' }, headers : { Authorization : 'Bearer ' + __access_token }, followRedirect : false }; request.get(options, function(err, res, body) { console.log("Location: " + res.body); expect(res.statusCode).to.equal(200); //data = JSON.parse(res.body); setTimeout(done, config.THROTTLE_DELAY); }); }); }); describe('Location API negative test cases', function() { var __msisdnExists = xxx; var __msisdnInvalid = xxx; it('should return an invalid PIN error', function(done) { var urlObj = { protocol: config.PROTOCOL, hostname: config.BASE_URL_LOCATION, pathname: config.URL_INVITATIONS + '/' + __msisdn, }; var postData = { validation_code: __validation_code }; var options = { url : url.format(urlObj), body: postData, json: true, headers : { Authorization : 'Bearer ' + __access_token }, followRedirect : false }; request.post(options, function(err, res, body) { console.log("Register device with PIN Invalid PIN Response: " + JSON.stringify(res.body)); expect(res.statusCode).to.equal(502); expect(res.body.code).to.equal('5020'); expect(res.body.message).to.equal('Invalid PIN'); //data = JSON.parse(res.body); setTimeout(done, config.THROTTLE_DELAY); }); }); it('should return an invalid MSISDN error (remove from whitelist)', function(done) { var urlObj = { protocol: config.PROTOCOL, hostname: config.BASE_URL_LOCATION, pathname: config.URL_WHITELIST + '/' + __msisdnInvalid, }; var options = { url : url.format(urlObj), headers : { Authorization : 'Bearer ' + __access_token }, followRedirect : false }; request.del(options, function(err, res, body) { console.log("Remove from whitelist Response (MSISDN invalid): " + res.body); expect(res.statusCode).to.equal(400); data = JSON.parse(res.body); expect(data.code).to.equal(4006); setTimeout(done, config.THROTTLE_DELAY); }); }); }); });
url, request, chai and config are variables of the dependency modules, the expect variable is going to be used for making the assertions. (Note that we can also use the npm assert module for making assertions).
The describe function denotes that start of a test suite and can have nested describe functions within it. In this case, positive test cases and negative test cases are contained in separate nested describe functions within a main describe function that represents the overall test suite.
The before function executes before all the test cases within the describe function. In this example, the before function is in the main describe function and hence executes before the 2 nested describe functions (positive and negative test cases). In this function, a HTTP GET request is made to the URL https://abc-test.apigee.net/v1/oauth/token with the query string parameters client_id, client_secret, grant_type and scope. The values of these parameters are taken from the config.js file. The expect assertion is used to check if the response status code is equal to 200. The JSON response in the body is parsed, asserted to not be empty using expect and its value is assigned to a global variable that is used in the nested describe functions. This function effectively implements the OAuth Client Credentials 2-legged grant type to make a call to the OAuth Token endpoint to get the OAuth access token. This access token will be used in all the test cases as the end-points of these test cases expect the access token for authorization.
Every test case is described using an “it” function, for example it('should send invite to device', function(done) {…}. The positive test cases described in this example do the following:
The negative test cases described in this example do the following:
The test cases are executed by typing the command mocha in the terminal from Mocha-Test/test. The output is in italics.
Location API Test Suite
Access Token: 7tBrAnQ5rsccUM0GtxLkg9L3dKlL
Location API positive test cases
Invitations Response: {"status":"400","code":"4002","message":"The user is already in whitelist"}
1)should send invite to device
Resend invitation Response:
{
"status": "400",
"code": "40010",
"message": "Invitation does not exist"
}
2)should resend invitation
Location:
{
"status": "400",
"code": "4009",
"message": "The requested quality of position cannot be provided"
}
3)should get location
Location API negative test cases
Register device with PIN Invalid PIN Response: {"status":"502","code":"5020","message":"Invalid PIN"}
✓ should return an invalid PIN error (6225ms)
Remove from whitelist Response (MSISDN invalid): { "status": 400,"code":
4006,"message": "MSISDN provided is not valid" }
✓ should return an invalid MSISDN error (remove from whitelist) (1913ms)
2 passing (59s)
3 failing
1) Location API Test Suite Location API positive test cases should send invite to device:
Uncaught AssertionError: expected 400 to equal 200
+ expected- actual
-400
+200
at Request._callback (test/test.js:128:43)
at Request.self.callback (node_modules/request/request.js:199:22)
at Request.<anonymous> (node_modules/request/request.js:1036:10)
at IncomingMessage.<anonymous> (node_modules/request/request.js:963:12)
at endReadableNT (_stream_readable.js:905:12)
2) Location API Test Suite Location API positive test cases should resend invitation:
Uncaught AssertionError: expected 400 to equal 200
+ expected- actual
-400
+200
at Request._callback (test/test.js:181:43)
at Request.self.callback (node_modules/request/request.js:199:22)
at Request.<anonymous> (node_modules/request/request.js:1036:10)
at IncomingMessage.<anonymous> (node_modules/request/request.js:963:12)
at endReadableNT (_stream_readable.js:905:12)
3) Location API Test Suite Location API positive test cases should get location:
Uncaught AssertionError: expected 400 to equal 200
+ expected- actual
-400
+200
at Request._callback (test/test.js:232:43)
at Request.self.callback (node_modules/request/request.js:199:22)
at Request.<anonymous> (node_modules/request/request.js:1036:10)
at IncomingMessage.<anonymous> (node_modules/request/request.js:963:12)
at endReadableNT (_stream_readable.js:905:12)
In this example, the positive test cases are all failing and the negative test cases are passing. Where there is a failure, we can see the expected and actual assertions and the stack trace of the failure. Where there is a successful execution of a test case, we can see a ✓ against the “it” clause.
This article covers only the basics and is intended to get started with Mocha. Further reading is recommended at https://mochajs.org for more advanced scenarios.
Thanks for sharing @Sathish Balasubramaniyan