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
Top comments (3)
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!
Folder Structure
Development
Installation
Iniatiate project
The script will created
package.json
file. lets install dev dependeciesVersion of dependencies maybe different with this tutorial. but its does not matter.
Unit Testing
create
user.service.test
file inside test/user/. see folder structureUnit Testing Description
Set up dependency module for unit test
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 spy, stub, and mocking framework with the Chai assertion library.
Test Setup environment
beforeEach : runs before every Runnable instance;
props : properties needed in the user service
sandbox.stub() : This is useful if you need to override an object’s property for the duration of a test, and have it restored when the test completes
sandbox.restore() : Restores all fakes created through sandbox.
afterEach: runs after every Runnable instance;
Create test case #1
Unit test anatomy [AAA]:
Arrange : everything we need to run the experiment. you may need to seed an object with some variable values or call a 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.
Create test case #2
unit testing will be on every layer. for
getById
we only need to make sure that this function calls get userRepository with the correct parametersCreate Service
Create user service inside src/user/. see folder structure
Run Unit Test
Hey! What is name of this boilerplate? It is your custom boilerplate?
This is very nice article. Thanks for share!
Some comments have been hidden by the post's author - find out more