NodeJS 8 from Scratch — Part 5

Anuj Baranwal
7 min readJul 16, 2018

--

In previous parts, we discussed about —

  • basics of nodeJS
  • debugging
  • asynchronous programming
  • call stack and event loop
  • promises
  • express and deploymeny

Part 1 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-1-a3c1431f1e15

Part 2 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-2-3035f8f46b09

Part 3 — https://medium.com/@anujbaranwal/nodejs-from-scratch-part-3-20956ec252a3

Part 4 — https://medium.com/@anujbaranwal/nodejs-from-scratch-part-4-d0aadf019c79

In this part, we will discuss about —

  • testing
  • mocha
  • assertion library — expect
  • testing async code and express applications
  • supertest

So, let’s get started. It is important to write unit test cases for the code that you write to make sure that the new features that are added to your application are well tested and that it doesn’t break any other features that are already existing in the application

For nodeJS applications, the testing setup is quite easy. We will be using mocha testing framework to set up our tests

Mocha

A feature-rich javascript test framework, meaning that it has all the tools needed to write tests, running on Node.js and in the browser, making asynchronous testing simple and fun.

The tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. The test cases can easily be organised and see how they relate to the file for which they are written.

mkdir nodejs_tests

in nodejs_tests , run npm init

in utils/utils.js

module.exports.add = (a, b) => a + b;

To install mocha, run npm install --save-dev mocha

This is a dependency that will be useful for us while development of the application. Therefore, it is not needed to be put with the app code.

in utils/utils.js

module.exports.add = (a, b) => {    return a + b;};

in utils/utils.test.js

const utils = require('./utils');it('should add two values correctly', () => {    const val = utils.add(3, 5);    if (val !== 8) {        throw new Error(`Value is not 8. It is ${val}.`); // if this gets executed, it will make the test to fail    }
});

When we run utils.test.js with mocha, it automatically injects the it and describe in the file. it represents a particular unit test case. It is a function provided by mocha. It defines a new test cases. It expects two params —

  • string describing the test case
  • function that tests that something work as expected

To make sure that mocha captures this file, we need to update the package.json

"scripts": {
"test": "mocha **/*.test.js" // recursively in all subdirectories - **
}

However, each time we have to run tests manually. We can also configure nodemon to watch for any changes and run tests automatically without us intervening

nodemon --exec "npm test"

in package.json

"scripts":{
"test": "mocha **/*.test.js",
"test-watch": "nodemon --exec \"npm test\""
}

Run npm run test-watch to watch for any changes and to run tests continuously.

Assertion Library

It has been earlier in test cases that we were throwing error to fail a particular test case. The code will get really messy if we keep failing test cases like that for other scenarios as well. To avoid this, we can make use of assertion libraries which provides simple and elegant syntax to test behaviours and values

There are many assertion libraries available. But the one that we will be using the expect library

npm install --save-dev expect@1.20.2

Some major changes has happened to expect in the latest version

With epxect , the tests will look like —

in utils/utils.test.js

const utils = require('./utils');const expect = require('expect');it('should add two values correctly', () => {const val = utils.add(3, 5);expect(val).toBe(8);expect(typeof val).toBe('number');});it('should square the number correctly', () => {const val = utils.square(3);expect(val).toBe(9);expect(typeof val).toBe('number');});

However, testing arrays and objects can not be done using toBe

it('should assert array and object inclusion, equality and exclusion correctly', () => {expect([1,2,3,4]).toInclude(1);expect([1,2]).toEqual([1,2]);expect({name: 'Anuj', age: 23}).toEqual({name: 'Anuj', age: 23});expect({name: 'Anuj', age: 23}).toInclude({age: 23});expect({name: 'Anuj', age: 23}).toExclude({name: 'Ankit'});});

in utils/utils.js

module.exports.enterUserName = (user, fullName) => {const names = fullName.split(' ');user.firstName = names[0];user.lastName = names[1];return user;};

in utils/utils.test.js

it('should fill the firstName and lastName correctly', () => {const user = {location: 'Surat', age: 25};const newUser = utils.enterUserName(user, 'Anuj Kumar');expect(newUser).toInclude({firstName: 'Anuj'});expect(newUser).toInclude({lastName: 'Kumar'});});

Branch — 11_Init

Now that we have learned how to test basic assertions in nodeJS. Let’s test some asynchronous code since this is what nodeJS app is pretty much filled with.

Testing asynchronous code

in utils/utils.js

module.exports.asyncAdd = (a, b, callback) => {setTimeout(() => {callback(a + b);}, 1000);};

in utils/utils.test.js

it('should add asynchronously correctly', () => { // <- 1utils.asyncAdd(3, 5, (sum) => {expect(sum).toBe(9); // <- 2});}); // this always passes??

The reason that the test always passes is that before the callback (2) gets executed, (1) was already returned. Thus it is marked as passed. To correct this —

in utils/utils.test.js

it('should add asynchronously correctly', (done) => { // <- 1utils.asyncAdd(3, 5, (sum) => {expect(sum).toBe(9); // <- 2
done();
});});

done tells mocha that it is a asynchronous operation and wait for it to get called and then only consider the test as finished. Therefore, until the (2) gets executed and done is called, it is not completed.

Testing express applications

It is important that we should be able to test express applications as this is what we will be doing mostly in the node series.

Let’s create a express server

in server/server.js

const express = require('express');const app = express();const port = process.env.PORT || 3000;app.get('/', (req, res) => {res.send('Test');})app.listen(port, () => {console.log(`Server listening on port: ${port}`);});module.exports.app = app;

To test express, we will make use of module called as supertest

npm install supertest --save-dev

in server/server.test.js

const request = require('supertest');const expect = require('expect');const app = require('./server').app;it('should respond with the correct status and response', (done) => {request(app).get('/').expect(200).expect('Test').end(done);});it('should respond with the 404 status and response as error: Page Not Found', (done) => {request(app).get('/err').expect(404).expect((res) => {expect(res.body).toInclude({error: 'Page Not Found!'}); // able to combine expect library with supertest}).end(done);});

Branch — 12_Init

Organising tests with describe

If the tests are running, you will se seeing output on the terminal —

Currently, all the tests are together. Thus it is not quite clear how the tests are related or grouped in a particular fashion. However, we can use describe to group test cases. It will be better to read and scan through the test cases in the logs

in utils/utils.test.js

const utils = require('./utils');const expect = require('expect');describe('Utils', () => {describe('#Add', () => {it('should add two values correctly', () => {const val = utils.add(3, 5);expect(val).toBe(8);expect(typeof val).toBe('number');});});describe('#Square', () => {it('should square the number correctly', () => {const val = utils.square(3);expect(val).toBe(9);expect(typeof val).toBe('number');});});describe('Other utils', () => {it('should assert array and object inclusion, equality and exclusion correctly', () => {expect([1,2,3,4]).toInclude(1);expect([1,2]).toEqual([1,2]);expect({name: 'Anuj', age: 23}).toEqual({name: 'Anuj', age: 23});expect({name: 'Anuj', age: 23}).toInclude({age: 23});expect({name: 'Anuj', age: 23}).toExclude({name: 'Ankit'});});it('should fill the firstName and lastName correctly', () => {const user = {location: 'Surat', age: 25};const newUser = utils.enterUserName(user, 'Anuj Kumar');expect(newUser).toInclude({firstName: 'Anuj'});expect(newUser).toInclude({lastName: 'Kumar'});});});describe('Async', () => {it('should add asynchronously correctly', (done) => { // <- 1utils.asyncAdd(3, 5, (sum) => {expect(sum).toBe(8); // <- 2done();});});});});

in server/server.test.js

const request = require('supertest');const expect = require('expect');const app = require('./server').app;describe('Server', () => {describe('GET /', () => {it('should respond with the correct status and response', (done) => {request(app).get('/').expect(200).expect('Test').end(done);});});describe('GET /err', () => {it('should respond with the 404 status and response as error: Page Not Found', (done) => {request(app).get('/err').expect(404).expect((res) => {expect(res.body).toInclude({error: 'Page Not Found!'});}).end(done);});});});

Above screenshot makes it quite clear how the tests are grouped and are easy to see in the logs too.

Test Spies

Sometimes, the function contains calls to function from other modules. In such cases, we need to make sure that the call to the other function was successful when our function was called. However, we don’t actually want to execute the other function’s code. In such case, we need to fake the other function. We use spies.

in spies/app.js

const db = require('./db');module.exports.signUp = (email, password) => {db.saveUser({email, password});};

in spies/db.js

module.exports.saveUser = (user) => {console.log(`Saving user ${user}.`);};

in spies/app.test.js

const expect = require('expect');it('should call the method once', () => {const spy = expect.createSpy(); // used when there is no function to spy on. tracks calls and argumentsspy();expect(spy).toHaveBeenCalled();});

However, we need to test that db.saveUser gets called when app.signUp is called.

We will be using rewire which gives us two important things — __set__ and __get__ can be used to set variables from a module. Let’s see it in practice —

npm install --save-dev rewire

in spies/app.test.js

const expect = require('expect');const rewire = require('rewire');const app = rewire('./app');it('should call the method once', () => {const spy = expect.createSpy();spy();expect(spy).toHaveBeenCalled();});it('should call db.saveUser when app.signUp is called', () => {const db = {saveUser: expect.createSpy()};app.__set__('db', db);app.signUp();expect(db.saveUser).toHaveBeenCalled();});

Branch — 13_Init

This is pretty much what we will be covering in test cases for a node application.

In this part, we discussed about —

  • testing
  • mocha
  • assertion library — expect
  • testing async code
  • testing express application — supertest
  • spies

Thanks. See ya in next part :)

Part 6.1 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-1-api-development-5dee11785d62

Part 6.2 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-2-api-development-f92f76eb3521

Part 6.3 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-3-api-development-9b046fed7364

Part 6.4 — https://medium.com/@anujbaranwal/nodejs-8-from-scratch-part-6-4-api-development-38d600a35ad9

--

--

Anuj Baranwal

Full Stack Web Developer @AdobeSystems Bangalore. Sometimes I code, I draw, I play, I edit and write