๐ก Running Tests Locally
Testing frameworks require Node.js. To practice:
npm install --save-dev jestnpm install --save-dev mocha chaiMaster automated testing to catch bugs before production. Learn unit tests, mocking, async testing, API testing, and test-driven development with Jest and Mocha.
Testing transforms "I think this works" into "I know this works." A solid test suite gives you confidence to refactor, fast feedback on bugs, and protection against regressions.
Jest is all-in-one: test runner, assertion library, mocking, coverage, and watch mode built in. Perfect for React and modern JavaScript projects.
Basic test structure with describe, test, and expect
// Jest - Basic Test Structure
// math.js - Code to test
function sum(a, b) {
return a + b;
}
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
module.exports = { sum, divide };
// math.test.js - Jest tests
const { sum, divide } = require("./math");
// describe() groups related tests
describe("sum()", () => {
// test() or it() defines a single test case
test("adds two positive numbers", () => {
expect(sum(2, 3)).toBe(5);
...Mocha is a minimal test runner paired with Chai for assertions. This combination gives you fine-grained control over your testing stack.
Flexible testing setup with Mocha and Chai assertions
// Mocha + Chai - Flexible Testing Setup
// Install: npm install --save-dev mocha chai
// math.js (same code)
function sum(a, b) {
return a + b;
}
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
module.exports = { sum, divide };
// test/math.test.js - Mocha + Chai tests
const { expect } = require("chai");
const { sum, divide } = require("../math");
describe("sum()", () => {
it("adds two positive numbers", () => {
const re
...Real apps are full of async code: API calls, timers, database queries. Both Jest and Mocha handle async testing with async/await and Promises.
Handle async/await, Promises, and rejections in tests
// Testing Asynchronous Code
// async.js - Async functions to test
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error("User not found");
}
return response.json();
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function delayedValue(value, ms) {
return new Promise(resolve => {
setTimeout(() => resolve(value), ms);
});
}
module.exports = { fetchUser, delay, delayedValue };
...Mocking isolates the code under test by replacing dependencies with controlled substitutes. Jest includes powerful mocking capabilities built-in.
Mock functions, modules, and fetch for isolated tests
// Mocking Functions, Modules & APIs
// --- JEST MOCKING ---
// 1. Mock functions
const mockFn = jest.fn();
mockFn("hello");
mockFn("world");
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith("hello");
// 2. Mock return values
const mockGet = jest.fn()
.mockReturnValue(10) // Always returns 10
.mockReturnValueOnce(5) // Returns 5 first time
.mockResolvedValue({ id: 1 }) // Returns Promise
.mockR
...๐ก When to Mock: Mock external services (APIs, databases), slow operations, unpredictable values (random, time), and modules with side effects. Keep tests fast, isolated, and deterministic.
Sinon provides spies, stubs, and mocks for Mocha. It's the companion library that gives Mocha the same mocking power Jest has built-in.
Spies, stubs, and mocks with Sinon library
// Sinon - Spies, Stubs & Mocks for Mocha
// npm install --save-dev sinon
const sinon = require("sinon");
const { expect } = require("chai");
// 1. Spies - Track function calls without changing behavior
describe("Spies", () => {
it("tracks calls", () => {
const callback = sinon.spy();
callback("hello");
callback("world");
expect(callback.calledTwice).to.be.true;
expect(callback.calledWith("hello")).to.be.true;
expect(callback.firstCall.args[0]).to.equal("he
...Test time-based logic like debounce, throttle, countdowns, and delays using fake timers. Control time programmatically for instant, reliable tests.
Use fake timers for debounce, throttle, and countdowns
// Testing Timers & Time-Based Logic
// timer.js - Code with timers
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
function countDown(seconds, onTick, onComplete) {
let remaining = seconds;
const interval = setInterval(() => {
remaining--;
onTick(remaining);
if (remaining === 0) {
clearInterval(interval);
onComplete();
}
}, 1000);
...Test Express/Node.js APIs by making HTTP requests and asserting responses. Supertest makes API testing clean and expressive.
Test Express APIs with HTTP requests and assertions
// Testing Node.js APIs with Supertest
// npm install --save-dev supertest
// app.js - Express application
const express = require("express");
const app = express();
app.use(express.json());
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
app.get("/users", (req, res) => {
res.json(users);
});
app.get("/users/:id", (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: "User not foun
...Coverage reports show which lines, branches, and functions your tests execute. Use coverage thresholds to enforce testing standards.
Measure code quality with coverage reports and thresholds
// Test Coverage - Measuring Code Quality
// Jest coverage: npm test -- --coverage
// Or add to package.json:
{
"jest": {
"collectCoverage": true,
"coverageReporters": ["text", "html", "lcov"],
"coverageDirectory": "coverage"
}
}
// Coverage output example:
// ---------------|---------|----------|---------|---------|
// File | % Stmts | % Branch | % Funcs | % Lines |
// ---------------|---------|----------|---------|---------|
// All files | 85.71 | 80 |
...TDD means writing tests before implementation. This drives better design and ensures every feature has test coverage from the start.
Write tests first, then implement to make them pass
// Test-Driven Development (TDD) Example
// TDD Workflow:
// 1. Write a FAILING test
// 2. Write minimum code to PASS
// 3. Refactor while keeping tests GREEN
// 4. Repeat
// Example: Build a password validator
// Step 1: Write failing tests FIRST
// passwordValidator.test.js
const { validatePassword } = require("./passwordValidator");
describe("validatePassword()", () => {
test("returns valid for strong password", () => {
expect(validatePassword("MyP@ssw0rd")).toEqual({
valid: t
...Follow these patterns to write tests that are reliable, maintainable, and actually catch bugs instead of just passing.
Write reliable, maintainable tests that catch bugs
// Testing Best Practices
// 1. Test behavior, not implementation
// โ BAD - Tests internal state
test("sets internal flag", () => {
const user = new User();
user.activate();
expect(user._isActive).toBe(true); // Private detail!
});
// โ
GOOD - Tests observable behavior
test("activated user can log in", () => {
const user = new User();
user.activate();
expect(user.canLogIn()).toBe(true);
});
// 2. One assertion per test (mostly)
// โ BAD - Multiple unrelated assertions
test("user
...Sign up for free to track which lessons you've completed and get learning reminders.