javascript

Unit Testing in Node.js With Jest

Antonello Zanini

Antonello Zanini on

Unit Testing in Node.js With Jest

Unit tests are essential for increasing the test coverage of a backend application, ensuring its reliability, functionality, and robustness. Jest has become one of the most popular solutions when unit testing in Node, due to its intuitive API, zero-configuration philosophy, and flexible approach to code transpilation.

In this guide, you will learn more about Jest, explore its features, and see it in action through a complete example.

Become a Jest unit testing expert!

What Is Jest?

Jest is a free, open-source JavaScript testing framework that checks the correctness of your codebase. It is focused on simplicity with a zero-config approach. It works with backend applications built in Node.js as well as frontend applications in React, Angular, Vue, and more. When it comes to code transpilation, it integrates natively with Babel, Vite, Parcel, and webpack.

With Jest, you can write unit, integration, and snapshot tests in both JavaScript and TypeScript.

At the time of writing, Jest boasts over 44.3k stars on GitHub and over 30 million weekly downloads on npm. These impressive figures place Jest among the top testing libraries in the JavaScript ecosystem.

Exploring Jest: Features, Capabilities, and Core Aspects

Now that you know what Jest is, it's time to discover why it's so popular by analyzing its main features.

Intuitive API

By default, a Jest test is a JavaScript or TypeScript file with the .test.{js,jsx,ts,tsx} or .spec.{js,jsx,ts,tsx} extension. Unless configured otherwise, Jest automatically looks for test files in the __tests__ directory or any sub-folder inside your project's root folder.

A single test file can contain more than one Jest unit test, as in the example below:

JavaScript
// __tests__/login.test.js describe("Login service", () => { test("should login successfully", () => { // test logic for login... }); test("should logout successfully", () => { // test logic for logout... }); });

As you can tell, Jest follows a Behavior Driven Development (BDD)-like syntax that allows you to organize tests using these functions:

  • describe(): Defines a group of related tests. This isn't required, but it helps organize tests into groups.
  • test(): Defines an individual test case. It accepts a name, a callback function containing the test logic, and an optional timeout value. This function is also available via the it() alias.
  • beforeAll(): A hook that runs the specified function before any of the tests in the file. If the function returns a Promise, Jest waits for the Promise to resolve before running the tests.
  • afterAll(): Runs the given function after all the tests in the file have completed.
  • beforeEach(): Runs a function before each of the tests in the file is executed.
  • afterEach(): Runs the defined function after each one of the tests in the file completes.

Note that if any of the last four functions returns a Promise, Jest waits for that Promise to resolve before continuing.

Human-Readable Assertions

Jest unit testing is based on the AAA pattern:

  • Arrange: Set up the conditions and prerequisites required for running the test (e.g., populating a demo database).
  • Act: Execute the code to test, which usually boils down to calling a single function.
  • Assert: Verify that the results match your expectations.

The Jest BDD-like interface presented earlier enables you to cover the first two steps of this pattern. To address the last one, Jest provides a complete assertion engine called expect(). This global function is used to check that values in a test meet certain conditions.

With almost 40 million weekly downloads, expect is the most popular JavaScript assertion library.

expect() accepts a value, and is then followed by some modifiers and matchers:

  • Modifiers: Alter the checking process. The most popular modifier function is not().
  • Matchers: Validate that the value meets specific conditions. Popular matcher functions include toBe(), toEqual(), toBeTruthy(), toBeFalsy(), toContain(), toHaveLength(), and toThrow().

By chaining these functions, you can define human-readable and easy-to-maintain assertions, as follows:

JavaScript
// the flavors array should not contain the "mango" item expect(getAllFlavors()).not.toContain("mango");

Additional Jest matchers can be found in the community-driven jest-extended project.

High Configurability

The design philosophy of Jest emphasizes working seamlessly out of the box in most scenarios while offering extensive customization options to meet diverse needs.

The official documentation particularly recommends defining a dedicated Jest configuration file. This can be a JavaScript, TypeScript, or JSON file, automatically detected when named jest.config.js|ts|mjs|cjs|json. Alternatively, you can manually specify the configuration file using the --config CLI flag.

The Jest configuration file must define an object containing all your custom configurations:

JavaScript
// jest.config.js const config = { verbose: true, // other options... }; module.exports = config;

There are more than 70 options available, including:

  • collectCoverage: Indicates whether the test coverage information should be collected and reported in the output.
  • errorOnDeprecated: Ensures that calling deprecated APIs triggers useful error messages, simplifying the process of identifying and addressing outdated code during upgrades.
  • maxConcurrency: Specifies the number of tests that are allowed to run at the same time when using test.concurrent(), an experimental version of test() to run tests concurrently.
  • testMatch: Specifies an array of glob patterns that Jest uses to detect test files in your project.
  • verbose: Indicates whether each individual test should be reported during the run.

See all available options in the Jest documentation.

Mocks for Functions, Classes, and Node.js Modules

Mocking is a technique used in testing to replace a component with a simplified version that mimics the behavior of the original. The idea is to isolate the code you are testing, simulating all its dependencies with controlled mocks.

Jest comes with robust mocking capabilities, supporting:

  • Mock functions: Also known as spies, they enable you to monitor and control the behavior of functions called indirectly by other code. You can define mock functions with the jest.fn() method.
  • Node modules: To mock entire Node.js modules, such as lodash or fs. Jest also supports mocking user-defined JavaScript modules.
  • ES6 class mocks: Mock ES6 classes imported into the files under test. Since ES6 classes are essentially constructor functions, you can mock them using either mock functions or actual ES6 classes.

This mocking functionality gives you the ability to create more focused and effective Jest unit test functions.

Dedicated Visual Studio Code Extension

Jest supports vscode-jest, a dedicated extension that makes testing more intuitive and enjoyable in VS Code. The extension, simply called “Jest” in the VS Code marketplace, is maintained by the Jest community. It works out of the box for most common Jest projects and supports all Jest features.

To get started with it, install the "Jest" extension in Visual Studio Code and reload the IDE. If the extension finds the global jest command, it will automatically run and monitor all your tests. You can now execute tests and see their status, errors, and coverage, as shown in the image below:

See jest tests

Integrating Jest into Node.js for Unit Testing

Let's learn how to set up Jest for unit testing in Node.js.

Introducing the Node.js Project

Suppose you already have a Node.js project in place, and you want to add some Jest unit tests to it. Let's assume your project has a utils folder containing a math.js file that exports the following pow() function:

JavaScript
// utils/math.js function pow(base, exponent) { // any number to the power of 0 is 1 if (exponent === 0) { return 1; } // handle negative exponents recursively if (exponent < 0) { return 1 / pow(base, -exponent); } // apply the power logic let result = 1; for (let i = 0; i < exponent; i++) { result *= base; } return result; } // other math utilities... module.exports = { pow };

This is just a custom implementation of the exponentiation mathematical operation.

Your goal is to write some unit tests in Jest to ensure the pow() function works as desired.

Setting Up Jest

Install Jest by adding the jest npm package to your project’s development dependencies:

Shell
npm install --save-dev jest

Otherwise, you can install it globally in your system with the command below:

Shell
npm install --global jest

Then, add a jest.config.js configuration file in the root folder of your project to enable verbose mode:

JavaScript
// jest.config.js const config = { verbose: true, }; module.exports = config;

As a final step, make sure the scripts section of your package.json file contains a test command to launch Jest:

JSON
"test": "npx jest"

Wonderful! You just set up Jest in Node.js for unit testing.

Defining a Unit Test

The official Jest documentation recommends adding unit tests directly to the folder that holds your code for testing. This keeps your tests organized and easily accessible alongside the code they are testing.

So, add a math.test.js inside the utils folder of your Node project and initialize it with the following logic:

JavaScript
// utils/math.test.js const { pow } = require("./math"); describe("pow", () => { test("should return 1 for base 5 and exponent 0", () => { expect(pow(5, 0)).toBe(1); }); test("should return 0 for base 0 and exponent 5", () => { expect(pow(0, 5)).toBe(0); }); test("should return 8 for base 2 and exponent 3", () => { expect(pow(2, 3)).toBe(8); }); test("should handle negative exponents", () => { expect(pow(2, -2)).toBe(0.25); }); test("should handle negative bases with integer exponents", () => { expect(pow(-2, 3)).toBe(-8); }); });

As you can see, this test file contains five different unit tests to check the behavior of the pow() function in different situations.

Your utils folder will now contain these two files:

Utils folder

Note that Visual Studio Code marks .test.js files with a different icon to distinguish them from the source code.

Amazing! All that remains is to launch the Jest tests and verify that they work.

Running a Test

You can launch Jest locally with this command:

Shell
npx jest

Or if you defined the test script in package.json as mentioned earlier, run:

Shell
npm run test

Otherwise, if you installed Jest globally, launch your tests with the command below in the root folder of your project:

Shell
jest

Regardless of the approach you take to executing Jest tests, the result will be something like this:

Shell
PASS utils/math.test.js pow should return 1 for base 5 and exponent 0 (3 ms) should return 0 for base 0 and exponent 5 should return 8 for base 2 and exponent 3 should handle negative exponents should handle negative bases with integer exponents Test Suites: 1 passed, 1 total Tests: 5 passed, 5 total Snapshots: 0 total Time: 3.881 s Ran all test suites.

Since our Node.js project only contains the math.test.js test file, Jest will execute only that. It runs all the unit tests defined within math.test.js, ensuring that each test produces the expected results (as specified in the test assertions).

Et voilà! You now know how to write unit tests with Jest in Node.js.

Next Steps For Mastering Jest

Next, you should learn how to integrate Jest with your CI/CD pipeline to automate your testing process.

To become proficient with Jest, explore its comprehensive documentation and GitHub repository. You'll find tons of examples and Jest troubleshooting tips. Additionally, the documentation includes a section with useful resources, such as the Jest Discord channel, where you can connect with the community for support and advice.

Wrapping Up

In this blog post, we explored what Jest is and how it simplifies writing unit tests in your Node.js application.

You now know:

  • What Jest is
  • The main features it provides when it comes to testing
  • How to define unit tests in Node.js with Jest

Thanks for reading!

P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.

P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.

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