Node.js Testing — Complete Guide to Jest and Mocha Testing Frameworks
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
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