NodeJS Boilerplate with Unit Testing - #1
Akhmad Fauzan
Posted on December 30, 2019
Introduction
Have we ever made a backend application from scratch. what programming language will be used, what about the performance. is there a lot of community to support it. and several other considerations. I will share a tutorial on how to create a boilerplate for a backend application using Javascript (NodeJS runtime) with Express Framework, how to manage dependencies between layers. and unit testing implementation with chai, sinon and mocha
Pre-requisites!
- node.js - runtime environment javascript
- ES6 What is Class, Arrow function in [ECMA SCRIPT 2016]
- Unit Testing What is unit testing
Folder Structure
.
├── .env
├── .eslintrc.js
├── .gitignore
├── config
│ └── database.js
├── package-lock.json
├── package.json
├── src
│ ├── MainApplication.js
│ ├── common
│ │ ├── middlewares
│ │ │ ├── index.js
│ │ │ └── validateSchema.js
│ │ └── repositories
│ │ ├── baseRepository.js
│ │ └── index.js
│ ├── index.js
│ ├── role
│ └── user
│ ├── userController.js
│ ├── userDependencies.js
│ ├── userModel.js
│ ├── userRepository.js
│ ├── userSchema.js
│ └── userService.js
└── test
├── MainApplication.test.js
├── common
│ ├── middlewares
│ │ └── validateSchema.test.js
│ └── repositories
│ └── baseRepository.test.js
├── fixtures
│ ├── index.js
│ ├── queryRequest.js
│ └── userController.js
├── setup.js
└── user
├── userController.test.js
├── userDependencies.test.js
└── userService.test.js
13 directories, 28 files
Development
Installation
Iniatiate project
$ npm init
The script will created package.json
file. lets install dev dependecies
npm install chai mocha nyc sinon sinon-chai --save-dev
Version of dependencies maybe different with this tutorial. but its does not matter.
{
"name": "nodejs_platform",
"version": "0.0.1",
"description": "NODEJS PLATFORM",
"main": "index.js",
"scripts": {
"start": "node src",
"lint": "eslint src test",
"test": "nyc mocha --recursive",
"test:coverage": "nyc report --reporter=lcov | npm run test"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.17.1",
"joi": "^14.3.1",
"lodash": "^4.17.15",
"mysql2": "^2.1.0",
"p-iteration": "^1.1.8",
"sequelize": "^5.21.3"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.19.1",
"mocha": "^6.2.2",
"nyc": "^15.0.0",
"sinon": "^8.0.0",
"sinon-chai": "^3.3.0"
},
"mocha": {
"globals": "global",
"require": "test/setup.js"
},
"nyc": {
"all": true,
"include": [
"src/**/*.js"
],
"exclude": [
"src/index.js"
]
}
}
Unit Testing
create user.service.test
file inside test/user/. see folder structure
const sinon = require('sinon');
const UserService = require('../../src/user/userService');
describe('User Service', () => {
const sandbox = sinon.createSandbox();
let props;
let userService;
beforeEach(() => {
props = {
userRepository: {
get: sandbox.stub(),
filterAndSort: sandbox.stub(),
},
};
userService = new UserService(props);
});
afterEach(() => {
sandbox.restore();
});
describe('#create', () => {
it('should return user when create user is successfully', () => {
const expectedUser = {
userName: 'bob',
email: 'bob@sit.com',
};
const result = userService.create();
expect(result).to.deep.equal(expectedUser);
});
});
describe('#getById', () => {
it('should call get userRepository with expected params', async () => {
const id = 'USR001';
const expectedParam = {
id,
};
await userService.getById(id);
expect(props.userRepository.get).to.calledWith(expectedParam);
});
});
describe('#filterAndSort', () => {
it('should call filterAndSort userRepository with expected params', async () => {
const expectedParam = {
filters: [
{
key: 'email',
operator: 'LIKE',
value: '@gmail',
},
],
sorts: [
{
key: 'email',
order: 'DESC',
},
],
page: {
limit: 10,
offset: 0,
},
};
await userService.filterAndSort(expectedParam);
expect(props.userRepository.filterAndSort).to.calledWith(expectedParam);
});
});
});
Unit Testing Description
Set up dependency module for unit test
const { expect, use } = require('chai');
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
const UserService = require('../../src/user/user.service');
use(sinonChai);
Chai : a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework
Sinon : Standalone and test framework agnostic JavaScript test spies, stubs and mocks
Sinon-Chai : provides a set of custom assertions for using the Sinon.JS like "to.have.been.calledWith"
Test Setup environment
const sandbox = sinon.createSandbox();
let props;
let userService;
beforeEach(() => {
props = {
userRepository: {
get: sandbox.stub(),
},
};
userService = new UserService(props);
});
afterEach(() => {
sandbox.restore();
});
beforeEach : runs before every Runnable instance;
props : properties needed for user service
sandbox.stub() : Override an object’s property for the test and mocking result function
sandbox.restore() : Restores all fakes created through sandbox.
afterEach: runs after every Runnable instance;
Create test case #1
describe('#create', () => {
it('should return user when create user is successfully', () => {
const expectedUser = {
userName: 'bob',
email: 'bob@sit.com',
}; // Arrange
const result = userService.create(); // Action
expect(result).to.deep.equal(expectedUser); // Assert
});
});
Unit test anatomy [AAA]:
Arrange : everything we need to run the experiment. you may need to seed an object with some variable values or init callback function
Action : represents the star of the unit testing show. we invoke the create method and capture the result.
Assert : something represents the essence of testing.
Better One Assert Per Test Method
Create test case #2
describe('#getById', () => {
it('should call get userRepository with expected params', async () => {
const id = 'userId';
const expectedParam = {
id,
};
await userService.getById(id);
expect(props.userRepository.get).to.calledWith(expectedParam);
});
});
unit testing will be on every layer. for getById
we only need to make sure that this function calls get userRepository with the correct parameter
Keep Unit Test Short, Sweet, and Visible
Create Service
Create user service inside src/user/. see folder structure
class UserService {
constructor(props) {
Object.assign(this, props);
}
async getById(id) {
const params = {
id,
};
return this.userRepository.get(params);
}
async filterAndSort(queryRequest) {
return this.userRepository.filterAndSort(queryRequest);
}
create() {
return {
userName: 'bob',
email: 'bob@sit.com',
};
}
}
module.exports = UserService;
Run Unit Test
mocha test --recursive
Posted on December 30, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.