Editor’s note: This comparison of Node.js unit testing frameworks was last updated on 3 July 2023 to include the most recent survey data, and other popular testing frameworks, such as Supertest and Webdriver.
In this Node.js unit testing guide, I’ll provide some reasons why you should unit test your Node apps, discuss what makes a good testing framework, and compare some of the most popular Node unit testing frameworks available today.
Unit testing is a software testing method in which individual pieces of code (usually the smallest piece of code that can be logically isolated in a system) are tested in isolation. Unit tests should be isolated so that there are no external dependencies.
There are many advantages associated with unit testing. First, unit testing makes it easier to identify bugs in code. Appropriate test cases should be written for every piece of code to ensure that they meet specifications and provide the desired output. Any changes that result in failing tests will indicate that an error or bug has been introduced. Additionally, unit testing makes it easier to narrow down the offending piece of code.
Second, unit tests act as self-documentation. A new team member can gain a better understanding of the codebase by going through unit tests. Additionally, the debugging process is made a lot easier with unit tests. This is because when the test fails, the focus will be on the latest changes made.
Refactoring code is also made easier with unit testing because changes can be verified using tests to ensure that the unit being tested still behaves in the desired manner. Unit testing also reduces the costs that can be incurred by fixing bugs or system outages are reduced.
Testing frameworks provide a set of reusable components or modules, such as test runners and utilities, for running automated tests. The testing framework is responsible for:
They are particularly useful when tests are an integral part of your continuous integration process. Frameworks are built for a specific type of testing: unit, integration, functional, or combinations of these.
There are countless frameworks out there. To pick something that works for your use case, you need to evaluate each framework based on your project needs and how effective you consider it to be for your team. Below are six key characteristics of a strong Node.js testing framework:
According to The State of JavaScript 2022, the most popular JavaScript testing frameworks and libraries in 2022 were Testing Library, Vitest, Jest, Cypress, Playwright, and Storybook. Rounding out the top ten are Puppeteer, Mocha, Jasmine, AVA, and WebdriverIO:
In this guide, we’ll compare eight of these Node.js unit testing frameworks:
Mocha has been around for a while; it was initially released in November 2011. However, unlike other frameworks like Jest and Jasmine, Mocha relies on third-party assertions, mocking, and spying tools like Sinon and Chai. This framework is very extensible and has a lot of plugins, extensions, and libraries designed to run on top of it.
Pros:
Cons:
Sample Mocha test:
const { expect } = require('chai'); describe('Sum numbers', () => { it('should add two numbers correctly', () => { const sum = 1 + 2; const expectedResult = 3; expect(sum).to.equal(expectedResult); }); });
Jest is a JavaScript testing framework developed and regularly maintained by Facebook. Its popularity has grown steadily since 2016, when only six percent of respondents to that year’s State of JS survey said they had used Jest before and would use it again. This figure climbed to a quarter of respondents in 2017 before reaching 40 percent in 2018. As of the most recent edition, a whopping 73 percent of JavaScript developers had tried Jest and planned to use it again.
Pros:
Cons:
Sample Jest test:
describe("Sum numbers", () => { test("it should sum two numbers correctly", () => { const sum = 1 + 2; const expectedResult = 3; expect(sum).toEqual(expectedResult); }) });
Developed by Pivotal Labs and released in 2010, Jasmine has been around for a lot longer than Jest. It aims to run on any JavaScript-enabled platform and is highly flexible and compatible with a variety of other testing frameworks and libraries, including Sinon and Chai. Due to its longevity, it has developed a significant community and enjoys ample support with many libraries, blog articles, and tutorials.
Pros:
Cons:
spec.js
)Sample Jasmine test:
describe("Sum numbers", function() { it("should sum two numbers correctly", function() { var sum = 1 + 2; var expectedResult = 3; expect(sum).toEqual(expectedResult); }); });
Minimalism is the focus of AVA. It has a simple API while still supporting advanced features. It achieves its blazing speed by running tests in parallel as separate Node processes. Unlike other testing frameworks such as Jest and Jasmine, it does not create test globals.
Pros:
npm init ava
Cons:
Sample Ava test:
import test from 'ava'; test('Sum numbers', t => { const sum = 1 + 2; const expectedResult = 3; t.equal(sum, expectedResult); });
Chai is an assertion library that follows a test-driven or behavior-driven development. You can easily pair it with any JavaScript testing framework such as Mocha for your application. Chai provides one of the best surface-level APIs and allows you to choose the interface that you are most comfortable with.
Pros:
expect
, should
, assert
Cons:
Sample Chai test:
// import chai const { expect } = require('chai'); // write your function function add(a, b) { return a + b; } // write assertions, you can also use `expect` or `should` interfaces describe('add', () => { it('should return the sum of two numbers', () => { const result = add(10, 20); expect(result).to.equal(30); });
Sinon is another powerful JavaScript library that is used to create tests for your applications. It provides key features such as stubs, spies, mocks, fake timers, etc. that are helpful when writing tests for isolated functions and logic. Sinon also integrates well with existing assertion libraries such as Chai and Jasmine. These assertions help by making code more robust based on the concept of stubs, spies, mocks, etc.
Pros:
Cons:
Sample Sinon test:
const sinon = require('sinon'); const { expect } = require('chai'); // a simple async function that fetches data from a remote server function fetchData(url) { return fetch(url) .then(response => response.json()) .catch(error => { console.log('API Error:', error); throw error; }); } describe('fetchData', () => { let fetchStub; // beforeEach comes from your testing framework like Mocha/Jasmine beforeEach(() => { // creating a stub for async fetch function fetchStub = sinon.stub().resolves({ json: () => ({ data: ‘Data’ }) }); }); // afterEach comes from your testing framework like Mocha/Jasmine afterEach(() => { // restoring fetchStub.restore(); }); // write test cases it('should fetch async data', async () => { // pass a url for async requests to get data const url = 'https://jsonplaceholder.typicode.com/todos'; // replace fetch function with `fetchStub` sinon.replace(window, 'fetch', fetchStub); // verify the data if it matches const result = await fetchData(url); expect(result).to.deep.equal({ data: ‘Data' }); }); }
Supertest is another library that is used for testing HTTP servers and making assertions against the response. It provides high-level API for sending HTTP requests and making assertions, this would help you with testing out server-side code.
Pros:
Cons:
Sample Supertest test:
const request = require('supertest'); const assert = require('assert'); const express = require('express'); const app = express(); app.get(‘/posts, function(req, res) { res.status(200).json({ name: ‘first_post }); }); request(app) .get(‘/posts’) .expect('Content-Type', /json/) .expect(200) .end((error, result) => { if(error){ throw error } })
Webdriver is an open source library that provides a collection of APIs for browser automation testing. These APIs allow you to control and interact with the browser programmatically. WebDriver supports a range of languages including Python, JavaScript, etc. It provides APIs powerful enough to work on different sets of browsers and frameworks.
Pros:
Cons:
Sample Webdriver test:
import { remote } from 'webdriverio' // initialize browser for automation & test const browserContainer = await remote({ capabilities: { browserName: 'chrome', 'goog:chromeOptions': { // disbale hardware accelaration args: process.env.CI ? ['headless', 'disable-gpu'] : [] } } }) await browserContainer.url('https://google.com’) const link = await browserContainer.$('=API') await link.click() // saves screenshot called "image" await browserContainer.saveScreenshot(‘./image.png')
The table below shows a comparison of the features across four Node unit testing frameworks: Mocha, Jest, Jasmine, and AVA:
Framework | Jasmine | Ava | Jest | Mocha | Chai | Sinon | Supertest | Webdriver |
---|---|---|---|---|---|---|---|---|
Open source | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
Built-in coverage reporting | No | No | Yes | No | No | No | No | No |
Parallel test running | No | Yes | Yes | No | No | No | No | No |
Snapshots | No | Yes | Yes | No | No | No | No | No |
Built-in spies | Yes | No | Yes | No | No | Yes | No | No |
Built-in mocking | Yes | No | Yes | No | No | Yes | No | No |
Built-in assertions | Yes | Yes | Yes | No | Yes | No | No | No |
ES2017 support | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
The best framework can vary based on your needs, project size, and other factors. What works now might not work in the future. It’s important to take both your current and future needs into consideration when choosing the right framework.
If you want to hit the ground running, you can’t go wrong with Jest. It’s an extremely fast framework, easy to set up, and has a lot of built-in features. If you’re looking for simplicity, AVA is your best bet. It’s minimal and streamlined but capable of handling various types of Node unit tests. It is also fairly fast.
Mocha is the best choice for someone who wants flexible configurations, as well as a choice of libraries to use together with it.
There are many Node unit testing frameworks and libraries available. In this guide, we focused on four of the most popular. Remember, the best choice for you will depend on your project’s unique goals and requirements.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
2 Replies to "Comparing the best Node.js unit testing frameworks"
Jasmine has supported async spec functions and waiting on returned promises for two years now. Not sure why lack of support is in the negatives section.
Jest, even in 2023, has issues testing normal, “not even modern anymore” ES module code. If you need a test framework for modern JS, Jest is flat out not an option.