Unit Testing Node.js Applications with Mocha and Chai
Unit testing is an essential technique for ensuring code quality and preventing bugs in Node.js applications. By testing small units of code, you can validate that each component functions as intended before piecing them together. This saves considerable time and money over the long run.
In this comprehensive guide, you‘ll learn how to harness the power of Mocha and Chai—two of the most popular JavaScript testing frameworks—to test your Node.js code.
An Introduction to Unit Testing
Before jumping into Mocha and Chai specifics, let‘s review some unit testing basics…
Unit testing refers to testing individual units of source code—functions, classes, modules, etc—in isolation. The purpose is to validate that each unique piece of code works as expected.
Unit testing provides many benefits:
- Finds bugs early in development
- Encourages modular code
- Documents intended functionality
- Saves time over manual testing
- Facilitates code reuse
- Simplifies integration
- Enables safe refactoring
By investing in a robust unit test suite, you can prevent many headaches down the road!
Why Mocha?
Mocha is one of the most popular JavaScript test frameworks, offering many helpful features for unit testing Node.js applications:
Flexibility – Mocha supports test-driven development (TDD) and behavior-driven development (BDD) interfaces. Use whichever style makes the most sense for your project!
Async Support – Mocha handles async operations like callbacks, promises, and async/await with ease—an essential requirement for most Node apps.
Descriptive Reporting – Mocha outputs clear, useful test reports to the console, allowing you to pinpoint failures quickly.
Customizable – Plug in various JS assertion libraries, test helpers, reporters, and more with Mocha‘s expansive ecosystem.
Feature-rich – Other handy features include test retries, test exclusion, before/after hooks, and more.
Active Community – As one of the most popular test frameworks, Mocha has great documentation and community support.
Why Chai?
Chai is an assertion library that enhances Mocha with expressive syntax for writing test assertions. With Chai, your test cases read nearly like plain English!
Some key benefits of using Chai include:
Fluent Assertions – Chai‘s expect, assert, and should interfaces promote readable tests.
Extensible – Chai is assertion library agnostic, integrates with several assertion styles.
Robust Assertions – Out-of-the-box, Chai supports powerful assertions like equality, types, strings, numbers, and more.
No Dependencies – Chai functions standalone without external dependencies.
Popular Pairing – Chai + Mocha is a popular combination for testing Node.js apps.
Now let‘s walk through installing Mocha and Chai then testing a simple Node.js application!
Installing Mocha and Chai
We‘ll use npm to install Mocha and Chai locally for this tutorial.
Step 1. Create a new Node.js project and initialize npm:
mkdir mocha-chai-demo
cd mocha-chai-demo
npm init -y
Step 2. Install Mocha and Chai locally as devDependencies:
npm install --save-dev mocha chai
And we‘re ready to write some tests!
Testing a Simple Node.js Module
Let‘s unit test a simple JavaScript module that works with shapes.
First, create a shapes.js file with the following code:
// shapes.js
class Shape {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
class Rectangle extends Shape {
area() {
return this.width * this.height;
}
}
class Triangle extends Shape {
area() {
return (this.width * this.height) / 2;
}
}
module.exports = {
Rectangle,
Triangle
}
This module exports two shape classes with an area() method to compute their respective areas.
Now let‘s validate this module using Mocha and Chai unit tests!
Writing Unit Tests with Mocha and Chai
By convention, unit tests live under a test folder in the root of your project. Let‘s add a shapes.test.js test file:
mocha-chai-demo
|-- node_modules
|-- shapes.js
|-- test
|-- shapes.test.js <-- Unit tests for shapes module
Here‘s how to test shapes.js using Mocha and Chai assertions:
// shapes.test.js
const { expect } = require("chai");
const { Rectangle, Triangle } = require("../shapes");
describe("Shape module", () => {
describe("Rectangle", () => {
it("should calculate the correct area", () => {
const rect = new Rectangle(5, 6);
expect(rect.area()).to.equal(30);
});
});
describe("Triangle", () => {
it("should calculate the correct area", () => {
const tri = new Triangle(8, 3);
expect(tri.area()).to.equal(12);
});
});
});
Let‘s break this down:
describe()blocks define test suitesit()blocks define individual test cases- Use Chai‘s
expectstyle assertions
To run the tests:
npm test
And you should see one passed test report!
There are a few key benefits on display here:
- The tests clearly validate the intended functionality
- Suite/spec organization keeps things tidy
- Failure output would help locate issues quicker
- As new shapes are added, tests help safely refactor
Let‘s enhance this example further…
More Examples and Best Practices
Here are some additional examples and tips for effective unit testing with Mocha and Chai:
Include negative test cases
Validate that invalid inputs handled properly:
it("should throw an error for invalid width", () => {
const invalidWidth = -5;
expect(() => {
new Rectangle(invalidWidth, 2);
}).to.throw();
});
Test error messages
Beyond testing a particular error is thrown, also validate the error messaging:
expect(() => {
//...
}).to.throw("Width must be positive");
Use before/after hooks
Handle test setup/teardown logic in hooks instead of repeating in each test:
describe("Rectangle", () => {
let rect;
beforeEach(() => {
rect = new Rectangle(5, 3);
});
afterEach(() => {
rect = null;
});
it("can calculate area", () => {
expect(rect.area()).to.equal(15);
});
});
Follow AAA pattern
Structure tests with Arrange, Act, Assert blocks:
it("should calculate total cost", () => {
// Arrange
const item = { price: 5, quantity: 3 };
// Act
const total = calculateTotal(item);
// Assert
expect(total).to.equal(15);
});
There are many additional best practices to writing effective, maintainable unit tests that validate logic without being brittle to changes.
Integration with CI Pipelines
To prevent regressions, it‘s important to run tests automatically whenever code changes. Mocha seamlessly integrates with continuous integration (CI) pipelines like Travis CI, Circle CI, and more.
For example, configuring Travis CI can look like:
# .travis.yml
language: node_js
node_js:
- 12
script:
- npm test # Run tests with Mocha!
Now tests will execute on every push—blocking bad code from merging!
Conclusion
Mocha and Chai provide an excellent combination for unit testing Node.js applications.
Mocha offers a flexible, feature-rich test framework while Chai brings expressive assertions with various interfaces. Together they make it easy to validate your code works as intended.
By continuously expanding your test suite coverage, you can prevent bugs, enable painless refactoring, and improve long-term productivity.
To learn more, check out the official documentation for Mocha and Chai.
Happy testing!