Skip to content

Node.js Testing — Complete Guide to Jest and Mocha Testing Frameworks

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Node.js Testing. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js testing frameworks like Jest and Mocha provide structure for writing automated tests that verify your code works correctly and prevent regressions.

What You'll Learn

By the end of this tutorial, you'll set up Jest and Mocha, write unit and integration tests, mock dependencies, measure Code Coverage, and test async code and database operations.

Why Testing Matters

Without tests, every deployment is risky. A single change can break unrelated features without warning. Tests catch bugs early, document expected behavior, and make Refactoring safe.

Real-World Use

A payment processing API has 500+ tests that run on every commit. When a developer changes the tax calculation, tests catch an edge case where discounts weren't applied, preventing a production outage.

Testing Learning Path

flowchart LR
  A[Debugging] --> B[Testing]
  B --> C[Express.js]
  C --> D[Express Middleware]
  D --> E[REST API]
  A --> F{You Are Here}
  style F fill:#f90,color:#fff

Jest Setup

npm install -D jest
// package.json
{
  "scripts": { "test": "jest", "test:watch": "jest --watch" }
}
// math.js
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
module.exports = { add, multiply };

// math.test.js
const { add, multiply } = require("./math");
test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).toBe(3);
});
test("multiplies 3 * 4 to equal 12", () => {
  expect(multiply(3, 4)).toBe(12);
});

Mocha Setup

npm install -D mocha chai
// package.json
{ "scripts": { "test": "mocha" } }
// math.test.js
const { expect } = require("chai");
const { add, multiply } = require("./math");
describe("Math functions", () => {
  it("should add two numbers", () => {
    expect(add(1, 2)).to.equal(3);
  });
  it("should multiply two numbers", () => {
    expect(multiply(3, 4)).to.equal(12);
  });
});

Testing Async Code

import fs from "node:fs/promises";

test("readFile returns content", async () => {
  const data = await fs.readFile("test.txt", "utf8");
  expect(data).toBe("Hello");
});

Mocking

// userService.js
const db = require("./db");
async function getUser(id) {
  const user = await db.findUser(id);
  if (!user) throw new Error("User not found");
  return user;
}

// userService.test.js
jest.mock("./db");
const db = require("./db");
const { getUser } = require("./userService");

test("getUser returns user when found", async () => {
  db.findUser.mockResolvedValue({ id: 1, name: "Alice" });
  const user = await getUser(1);
  expect(user.name).toBe("Alice");
});

Code Coverage

npx jest --coverage
// package.json
{
  "scripts": {
    "test": "jest --coverage",
    "test:coverage": "jest --coverage --collectCoverageFrom='src/**/*.js'"
  }
}

Common Mistakes

1. Testing Implementation Details

Test behavior, not internal implementation. Refactoring shouldn't break tests if they test the right things.

2. Not Testing Edge Cases

Empty arrays, null values, boundary conditions, and error paths are where most bugs hide.

3. Using Real External Services

Tests that hit real APIs or databases are slow, flaky, and require infrastructure. Mock external dependencies.

4. Writing Tests That Are Too Large

Each test should test one thing. Large tests with multiple expectations are hard to debug when they fail.

5. Ignoring Test Performance

Slow test suites discourage running tests. Keep unit tests fast (milliseconds). Use integration tests sparingly.

Practice Questions

1. What is the difference between unit and integration tests?

Unit tests isolate a single function/module. Integration tests verify that multiple modules work together correctly.

2. What does jest.mock do?

It replaces a module with an auto-mock, letting you control what functions return without calling the real implementation.

3. How do you test a function that throws an error?

Wrap the call in a function: expect(() => fn()).toThrow(). For async: expect(fn()).rejects.toThrow().

4. What is code coverage?

Code coverage measures what percentage of your code is executed during tests. 100% coverage doesn't mean bug-free, but low coverage means untested code.

5. Challenge: Write tests for a function that validates email addresses.

function validateEmail(email) {
  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!email) throw new Error("Email required");
  if (!re.test(email)) throw new Error("Invalid email format");
  return true;
}
// Tests
test("valid email returns true", () => expect(validateEmail("a@b.com")).toBe(true));
test("missing @ throws", () => expect(() => validateEmail("invalid")).toThrow());
test("empty string throws", () => expect(() => validateEmail("")).toThrow());

FAQ

Should I use Jest or Mocha?

Jest is more opinionated (built-in assertions, mocking, coverage). Mocha is more flexible (choose your assertion library). Jest is recommended for new projects.

How do I test Express routes?

Use supertest: request(app).get('/api/users').expect(200). It starts the app and makes HTTP requests.

What is snapshot testing?

Jest snapshots capture a value and compare it on subsequent runs. Useful for testing rendered output that shouldn't change unexpectedly.

How do I run a single test file?

Jest: npx jest path/to/test.js. Mocha: npx mocha path/to/test.js

What is TDD?

Test-Driven Development: write tests before implementation. Red (test fails) → Green (test passes) → Refactor.

Mini Project: Test a Math Library

Write comprehensive tests for a math utility library.

// math.js
function factorial(n) {
  if (n < 0) throw new Error("Negative input");
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}
function isPrime(n) {
  if (n < 2) return false;
  for (let i = 2; i <= Math.sqrt(n); i++) {
    if (n % i === 0) return false;
  }
  return true;
}
module.exports = { factorial, isPrime };

// math.test.js
test("factorial of 0 is 1", () => expect(factorial(0)).toBe(1));
test("factorial of 5 is 120", () => expect(factorial(5)).toBe(120));
test("factorial of negative throws", () => expect(() => factorial(-1)).toThrow());
test("isPrime for 2", () => expect(isPrime(2)).toBe(true));
test("isPrime for 4", () => expect(isPrime(4)).toBe(false));
test("isPrime for 17", () => expect(isPrime(17)).toBe(true));

What's Next

Express.js Routing Express Middleware REST API Express

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro