Testing is a crucial aspect of software development, ensuring that your code behaves as expected and remains reliable over time. In Node.js, unit testing is a fundamental practice that involves testing individual units or components of your application in isolation. This chapter will guide you through the process of writing unit tests in Node.js, from the basics to more advanced techniques, with a focus on practical examples.
Unit testing is a software testing technique where individual units or components of the software are tested in isolation from the rest of the application. A “unit” can be a function, method, or object, and the goal of unit testing is to validate that each unit of the software performs as expected.
Consider a simple function that adds two numbers:
// File: math.js
function add(a, b) {
return a + b;
}
module.exports = add;
A unit test for this function would involve checking whether the function correctly adds two numbers.
Unit testing offers several benefits, including:
To start testing in Node.js, you’ll need a testing framework. Mocha is a popular choice for its simplicity and flexibility, and Chai is an assertion library that works well with Mocha.
Initialize Your Project:
If you haven’t already, create a Node.js project and initialize it:
mkdir nodejs-testing
cd nodejs-testing
npm init -y
2.Install Mocha and Chai:
Install Mocha and Chai as development dependencies:
npm install --save-dev mocha chai
3.Project Structure:
Your project should look like this:
nodejs-testing/
├── test/
│ └── math.test.js
├── math.js
└── package.json
Let’s create our first test for the add
function.
File: test/math.test.js
// File: test/math.test.js
const add = require('../math');
const { expect } = require('chai');
describe('Addition Function', () => {
it('should add two numbers correctly', () => {
const result = add(2, 3);
expect(result).to.equal(5);
});
});
result
equals 5
.Add a test script to your package.json
:
"scripts": {
"test": "mocha"
}
Run the test:
npm test
// Output
Addition Function
✓ should add two numbers correctly
1 passing (10ms)
Let’s add more functionality to our math.js
file and write tests for it.
math.js
// File: math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
Update your test file to include tests for the subtract
function.
test/math.test.js
npm test
// Output
Math Functions
✓ should add two numbers correctly
✓ should subtract two numbers correctly
2 passing (10ms)
// File: test/math.test.js
const { add, subtract } = require('../math');
const { expect } = require('chai');
describe('Math Functions', () => {
it('should add two numbers correctly', () => {
const result = add(2, 3);
expect(result).to.equal(5);
});
it('should subtract two numbers correctly', () => {
const result = subtract(5, 3);
expect(result).to.equal(2);
});
});
Asynchronous code is common in Node.js, and testing it requires a different approach.
Consider a function that returns a promise.
math.js
// File: math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiplyAsync(a, b) {
return new Promise((resolve) => {
setTimeout(() => resolve(a * b), 1000);
});
}
module.exports = { add, subtract, multiplyAsync };
File: test/math.test.js
// File: test/math.test.js
const { add, subtract, multiplyAsync } = require('../math');
const { expect } = require('chai');
describe('Math Functions', () => {
it('should add two numbers correctly', () => {
const result = add(2, 3);
expect(result).to.equal(5);
});
it('should subtract two numbers correctly', () => {
const result = subtract(5, 3);
expect(result).to.equal(2);
});
it('should multiply two numbers asynchronously', async () => {
const result = await multiplyAsync(2, 3);
expect(result).to.equal(6);
});
});
multiplyAsync
function.
npm test
// Output
Math Functions
✓ should add two numbers correctly
✓ should subtract two numbers correctly
✓ should multiply two numbers asynchronously (1005ms)
3 passing (1s)
If you’re dealing with callbacks, you can use done
to signal Mocha when the test is complete.
File: math.js
// File: math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiplyCallback(a, b, callback) {
setTimeout(() => {
callback(a * b);
}, 1000);
}
module.exports = { add, subtract, multiplyCallback };
File: test/math.test.js
// File: test/math.test.js
const { add, subtract, multiplyCallback } = require('../math');
const { expect } = require('chai');
describe('Math Functions', () => {
it('should add two numbers correctly', () => {
const result = add(2, 3);
expect(result).to.equal(5);
});
it('should subtract two numbers correctly', () => {
const result = subtract(5, 3);
expect(result).to.equal(2);
});
it('should multiply two numbers using callback', (done) => {
multiplyCallback(2, 3, (result) => {
expect(result).to.equal(6);
done();
});
});
});
npm test
// Output
Math Functions
✓ should add two numbers correctly
✓ should subtract two numbers correctly
✓ should multiply two numbers using callback (1005ms)
3 passing (1s)
Mocha provides hooks that allow you to run code before and after your tests.
Use before
and after
hooks to set up or clean up resources.
File: test/math.test.js
// File: test/math.test.js
const { add, subtract, multiplyCallback } = require('../math');
const { expect } = require('chai');
describe('Math Functions', () => {
let testValue;
before(() => {
testValue = 10; // Initialize test data
});
after(() => {
testValue = null; // Clean up test data
});
it('should add two numbers correctly', () => {
const result = add(2, 3);
expect(result).to.equal(5);
});
it('should subtract two numbers correctly', () => {
const result = subtract(5, 3);
expect(result).to.equal(2);
});
it('should multiply two numbers using callback', (done) => {
multiplyCallback(2, 3, (result) => {
expect(result).to.equal(6);
done();
});
});
});
These hooks run before and after each test in the suite.
File: test/math.test.js
// File: test/math.test.js
const { add, subtract, multiplyCallback } = require('../math');
const { expect } = require('chai');
describe('Math Functions', () => {
let a, b;
beforeEach(() => {
a = 2;
b = 3;
});
afterEach(() => {
a = 0;
b = 0;
});
it('should add two numbers correctly', () => {
const result = add(a, b);
expect(result).to.equal(5);
});
it('should subtract two numbers correctly', () => {
const result = subtract(5, 3);
expect(result).to.equal(2);
});
it('should multiply two numbers using callback', (done) => {
multiplyCallback(a, b, (result) => {
expect(result).to.equal(6);
done();
});
});
});
a
and b
before each test.a
and b
after each test.Sinon is a library used for creating spies, mocks, and stubs, allowing you to test code that depends on external services.
Install Sinon:
npm install --save-dev sinon
File: test/math.test.js
// File: test/math.test.js
const sinon = require('sinon');
const { multiplyAsync } = require('../math');
const { expect } = require('chai');
describe('Math Functions', () => {
it('should multiply two numbers asynchronously', async () => {
const stub = sinon.stub().resolves(6);
const result = await stub(2, 3);
expect(result).to.equal(6);
});
});
Supertest is a library used for testing HTTP requests.
Install Supertest:
npm install --save-dev supertest
File: server.js
// File: server.js
const express = require('express');
const app = express();
app.get('/add', (req, res) => {
const { a, b } = req.query;
const result = Number(a) + Number(b);
res.json({ result });
});
module.exports = app;
File: test/server.test.js
// File: test/server.test.js
const request = require('supertest');
const app = require('../server');
const { expect } = require('chai');
describe('GET /add', () => {
it('should add two numbers via query params', async () => {
const response = await request(app).get('/add').query({ a: 2, b: 3 });
expect(response.body.result).to.equal(5);
});
});
/add
route.a
and b
with the request.
npm test
// Output
GET /add
✓ should add two numbers via query params
1 passing (10ms)
Code coverage measures how much of your code is being tested. nyc is a popular tool for this purpose.
Install nyc:
npm install --save-dev nyc
Update your package.json
:
"scripts": {
"test": "nyc mocha"
}
Run the tests with coverage:
npm test
// Output
=============================== Coverage summary ===============================
Statements : 100% ( 8/8 )
Branches : 100% ( 2/2 )
Functions : 100% ( 3/3 )
Lines : 100% ( 8/8 )
================================================================================
test
folder, mirroring the structure of your source files..test.js
as a suffix for test files.
project-root/
├── src/
│ └── math.js
└── test/
└── math.test.js
Unit testing in Node.js is an essential practice that ensures your code is robust, maintainable, and reliable. By following the techniques and best practices outlined in this chapter, you can write effective unit tests that cover a wide range of scenarios, from simple functions to complex asynchronous code and external dependencies.Happy coding !❤️