javascript

How To Write Unit Tests in Node.js Using Mocha

Antonello Zanini

Antonello Zanini on

How To Write Unit Tests in Node.js Using Mocha

Increasing the test coverage of a backend application is key to ensuring its reliability, functionality, and robustness. Thanks to its intuitive API and agnostic approach to assertions, Mocha has emerged as one of the most popular choices for unit testing in Node.js.

In this guide, you'll learn what Mocha is, understand how it works, explore its features, and see it in action with a complete example.

Time to become a Mocha unit testing expert!

What Is Mocha for Node?

Mocha is a JavaScript test framework that runs on Node.js and in the browser. It supports Behavior-Driven Development (BDD), Test-Driven Development (TDD), Exports, QUnit, and Require-style testing interfaces. Its main goal is to allow developers to write tests for their backend and frontend applications, ensuring that their code behaves as expected.

Mocha works with any Node-based backend or web application that runs in a browser. While it supports integration tests — and even end-to-end tests — it is mainly known for unit testing.

At the time of writing, Mocha boasts over 22.7k stars on GitHub and over 10 million weekly npm downloads. These impressive statistics make it one of the most beloved and used unit testing libraries in the JavaScript community.

Mocha's Key Concepts and Features

Now that you know what Mocha is, let's dig into its features to find out what makes it so popular!

Complete BDD-Like API

By default, Mocha looks for tests in the ./test/ folder, but you can configure it to search for tests in other folders as well. The convention is to give JavaScript test files a .spec.{js,cjs,mjs} extension. "spec" stands for "specification," a term that comes from BDD testing. In this world, developers are encouraged to describe (i.e., specify) what software does rather than how it does it.

For example, a Mocha test file containing several unit tests looks like this:

JavaScript
// /test/login.spec.js describe("Login service", function () { it("should login successfully", function () { // test logic for login... }); it("should logout successfully", function () { // test logic for logout... }); });

Mocha tests follow the BDD interface and can be specified through these methods:

  • describe(): Defines a test suite, which is a collection of related tests. It takes a string for the suite name and a callback function containing the individual tests. context() is an alias for this function.
  • it(): Defines an individual test case. It takes a string for the test name and a callback function containing the test logic. specify() is an alias for this function.
  • before(): A hook that runs once before all tests in the suite, such as setup tasks.
  • after(): A hook that runs once after all tests in the suite, such as cleanup tasks.
  • beforeEach(): A hook that runs before each test in the suite. It is useful for setting up the conditions required by each test in the suite.
  • afterEach(): A hook that runs after each test in the suite. It is typically used to clean or restore the state after each test has run.

Mocha tests follow the popular Arrange, Act, Assert — AAA pattern.

Intuitive Flow of Execution

When you launch the mocha command in your Node.js project folder, this is what Mocha does:

  1. Loads options from the configuration files, if present.
  2. Processes any command-line options provided.
  3. If known flags for the node executable are found, it spawns node in a child process and executes with those flags. Otherwise, it doesn't spawn a child process.
  4. Loads modules specified via the --require option, if present. If a file loaded this way contains known Mocha-specific exports, it registers these. Otherwise, it ignores any exports from these modules.
  5. Validates any custom reporters or interfaces loaded via --require or other methods.
  6. Discovers test files in the ./test/ folder or the specified path.
  7. Loads these test files in no particular order. When loading a test file, Mocha registers all its test suites and hooks but doesn't execute them.
  8. Runs global setup fixtures, if any.
  9. Starting with the root test suite, Mocha executes any before() hooks. Then, for each test, it runs any beforeEach() hooks, the test itself, and any afterEach() hooks. If the current suite has a child suite, it repeats the actions in this step for each child suite. Note that each child suite inherits any beforeEach() and afterEach() hooks defined in its parent. Finally, it executes any after() hooks.
  10. Prints a final summary, if applicable.
  11. Runs global teardown fixtures, if any.

As you can tell, Mocha executes test suites one at a time by default.

Support for Parallel Execution

The mocha command line tool comes with the --parallel option to execute test suites in parallel. When specified, Mocha creates a pool of subprocesses to run multiple tests simultaneously. Each test file is loaded into a queue and executed as workers become available. So, Mocha test files are run in parallel, but individual tests inside these files are executed serially.

The default number of workers is the number of CPU cores minus one, but you can customize that via the --jobs <count> option. Depending on the number and nature of your tests, you may find a significant performance benefit when running tests in parallel.

Assertion Agnostic

Mocha enables you to use any assertion library you wish, including:

  • chai: A well-known BDD assertion library for Node.js that can be paired with any JavaScript testing framework.
  • assert: A simple set of assertion functions based on the Node.js Assert API.
  • node:assert: A built-in assertion library provided by Node.js.
  • should.js: A framework-agnostic assertion library that can extend the object prototypes with functions that allow you to express how that object should behave.
  • expect.js: A minimalistic BDD-style assertion library for Node.js and the browser. This is the assertion library used by Jest, another popular JavaScript testing framework.

In other words, Mocha provides everything you need to perform the "Arrange" and "Act" steps of the AAA pattern. The "Assert" step is left to you. This design approach keeps the library lightweight and makes it more future-proof, as it isn't tightly coupled with a specific assertion library.

Sync and Async Node Code Testing

Mocha supports testing synchronous and asynchronous Node code out of the box.

When testing synchronous code, Mocha will automatically move on to the next test once the it() function completes:

JavaScript
describe("Array", function () { it("should return -1 when the value is not present", function () { [1, 2, 3, 4].indexOf(8).should.equal(-1); }); });

Since the indexOf() method is synchronous, Mocha will proceed to the next test once the test logic ends.

When working with asynchronous code, things get a little trickier and there are a few possible approaches.

For asynchronous tests, you can add an argument to the it() callback. This is a callback function and is usually called done. When specified, Mocha will wait until the done() function is called to complete the test. done accepts an error as its first argument, or nothing if the test passes:

JavaScript
// imports and test db setup ... describe("User service", function () { it("should create a new user without errors", function (done) { var newUser = { name: "Maria", surname: "Williams", age: 42, }; User.save(newUser) .then(function () { done(); }) .catch(function (error) { done(error); }); }); });

Instead of using the done() callback, you can also directly return a Promise. Mocha will automatically wait for the Promise to be resolved or rejected.

JavaScript
// imports and test db setup ... describe("User service", function () { it("should respond with matching records", function () { return User.find().should.eventually.have.length(3); }); });

Note that you can either use the done() callback or return a Promise, but not both. Otherwise, your test will fail with the following error:

Shell
Resolution method is overspecified. Specify a callback *or* return a Promise; not both.

A cleaner approach to asynchronous code testing is to use the async/await syntax:

JavaScript
// imports and test db setup ... describe("User service", function () { it("should respond with matching records", async function () { const users = await User.find(); users.should.have.length(3); }); });

Getting Started With Mocha in Node

In this step-by-step section, you'll learn how to write your first unit test for Node using Mocha.

For faster setup or to have the code on hand, clone the GitHub repository that supports this article:

Shell
git clone https://github.com/Tonel/mocha-demo

Enter the project folder in the terminal and install the project dependencies:

Shell
cd mocha-demo npm install

Let's see how to achieve the same result in a guided tutorial!

Setting Up a Node Project

First, make sure you have Node installed on your machine. Then, create a folder for your project and enter it in the terminal:

Shell
mkdir mocha-demo cd mocha-demo

Inside the folder, initialize a new Node.js project with the init command:

Shell
npm init -y

Awesome! The mocha-demo directory now contains a blank Node.js project. Load it into your favorite JavaScript IDE.

Open package.json and make sure to turn your project into an ES module by adding:

JSON
"type": "module"

You're ready to define an Express backend. The idea is to implement an API that accepts a number n and returns the first n elements of the Fibonacci sequence.

Add express to the project dependencies:

Shell
npm install express

Next, add the controllers/ and services/ folders to the project's root folder:

Add controllers and services to project folder

Define a MathController object in a math.js file inside ./controllers/ as below:

JavaScript
// controllers/math.js import { MathService } from "../services/math.js"; export const MathController = { getFibonacci: (req, res) => { // read the parameter from the request URL // and validate it const n = req.params.n; if (isNaN(n) || n < 1) { return res.status(400).json({ error: "Invalid number" }); } // generate the Fibonacci sequence // and return it const sequence = MathService.generateFibonacci(n); res.json({ sequence: sequence, }); }, };

This specifies the handler function for the Fibonacci generator endpoint. The business logic is encapsulated in the MathService object. Define it in a math.js file inside ./services as follows:

JavaScript
// services/math.js export const MathService = { generateFibonacci: (n) => { // generate the Fibonacci sequence from 0 to n // and then return it const fib = [0, 1]; for (let i = 2; i < n; i++) { fib[i] = fib[i - 1] + fib[i - 2]; } return fib.slice(0, n); }, };

Now, create an index.js file, import MathController, initialize an Express server, and register the Fibonacci route:

JavaScript
// index.js import express from "express"; import { MathController } from "./controllers/math.js"; // initialize the Express server const app = express(); // register the Fibonacci API route app.get("/api/v1/fibonacci/:n", MathController.getFibonacci); // start the Express server const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); });

Your Node.js sample project should now contain this file structure:

Node file structure

As a final step, add a dev command in the scripts section of your package.json file to launch the Express server:

JSON
"dev": "node index.js"

You can now run your Node application locally with:

Shell
npm run dev

Your backend server will start on port 3000.

To test the API, you can make a request to the Fibonacci endpoint with cURL as follows:

Shell
curl 'http://localhost:3000/api/v1/fibonacci/10'

As expected, you'll get the following result:

JSON
{ "sequence": [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] }

Wonderful! You now have a Node application to unit test with Mocha.

Installing Mocha

Install Mocha by adding the mocha npm package to your project's development dependencies:

Shell
npm install --save-dev mocha

Then, add a test folder to your project's root directory:

Shell
mkdir test

This will contain all your Mocha unit tests.

Since Mocha is assertion agnostic, you must install a third-party assertion library. In this example, we're going to use Chai.js. Add it as a dev dependency with the command below:

Shell
npm install --save-dev chai

Note that since version 5.0, Chai is an ESM-only Node.js library.

Here we go! It's time to define your first unit test in Mocha.

Adding a Unit Test

Inside the ./test/ folder, add a math.spec.js file, defined as follows:

JavaScript
// test/math.spec.js import { expect } from "chai"; import { MathService } from "../services/math.js"; describe("MathService", function () { describe("#generateFibonacci", function () { it("should return the first 2 Fibonacci numbers", function () { const result = MathService.generateFibonacci(2); expect(result).to.deep.equal([0, 1]); }); it("should return the first 10 Fibonacci numbers", function () { const result = MathService.generateFibonacci(10); expect(result).to.deep.equal([0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); }); }); });

This imports MathService and specifies a Mocha unit test for each possible scenario. In particular, it tests the generateFibonacci() function against 2 and 10, respectively. Note the use of the to.deep.equal() assertion to test for deep equality between the result array and the expected array.

As recommended in the Node best testing practices, unit tests should be simple and ideally involve a single call to a function to test. Just like in the example above!

Executing the Test

Run your Mocha unit tests inside the test files with this command:

Shell
npx mocha

This will start the Mocha flow of execution, finding all test files inside the ./test/ folder and executing the test suites inside them one at a time. In this case, it is just the test suite in the math.spec.js file.

To make it easier to launch Mocha, you can also add a special test command in the scripts section of your package.json file:

JSON
"test": "npx mocha"

Now you can run your tests with:

Shell
npm run test

Regardless of the approach you decide to follow, here's the result:

Shell
MathService #generateFibonacci should return the first 2 Fibonacci numbers should return the first 10 Fibonacci numbers 2 passing (12ms)

Et voilà! You're now a Mocha unit test expert!

Wrapping Up

In this post, we explored what Mocha is and how it helps you increase test coverage in your Node.js application through unit tests.

You now know:

  • What Mocha is
  • The features and characteristics it provides for testing
  • How to use it to write unit tests in Node

Thanks for reading!

Wondering what you can do next?

Finished this article? Here are a few more things you can do:

  • Share this article on social media
Antonello Zanini

Antonello Zanini

Guest author Antonello is a software engineer, but prefers to call himself a Technology Bishop. Spreading knowledge through writing is his mission.

All articles by Antonello Zanini

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps