Testing in JavaScript
Unit tests, integration tests, mocking, and the tools — Jest, Vitest, and the testing-library approach explained plainly.
Assigning a mock to a global, such as global.fetch = vi.fn()..., replaces it for every test that runs afterwards in that file, not just the current one. A completely unrelated test then fails, but only when it runs after the mocking test, and passes when you run it alone. It's one of the most confusing failures to debug, because the broken test's own code is fine.
// shown with Node's built-in runner (node --test); Vitest and Jest behave the same way
import { test } from "node:test";
import assert from "node:assert/strict";
async function getUser(id) {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) throw new Error("User not found");
return res.json();
}
test("getUser throws on 404", async () => {
globalThis.fetch = async () => ({ ok: false, status: 404 }); // replaced... never put back
await assert.rejects(getUser(999), /User not found/);
});
test("reads settings from a data: URL", async () => { // an unrelated test
const res = await fetch('data:application/json,{"theme":"dark"}');
assert.equal((await res.json()).theme, "dark");
});
// ok 1 - getUser throws on 404
// not ok 2 - reads settings from a data: URL error: 'res.json is not a function'
// (run test 2 on its own and it passes)Writing code that checks your code
Testing means writing a second program whose job is to check that your first program works. Every time you add a feature or fix a bug, you run the tests — they tell you instantly if anything broke. This is how professional developers ship code with confidence.
What a test looks like
// Using Jest or Vitest (same syntax):
import { describe, test, expect } from "vitest";
import { add, divide } from "./math.js";
describe("math utilities", () => {
test("add returns the sum", () => {
expect(add(2, 3)).toBe(5); // 2+3 should equal 5
expect(add(-1, 1)).toBe(0); // -1+1 should equal 0
expect(add(0, 0)).toBe(0);
});
test("divide returns correct result", () => {
expect(divide(10, 2)).toBe(5);
expect(divide(7, 2)).toBe(3.5);
});
test("divide throws on zero divisor", () => {
expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
});
});The most useful matchers
// Equality
expect(value).toBe(5); // strict equality (===)
expect(obj).toEqual({a:1}); // deep equality (nested objects)
expect(arr).toStrictEqual([1,2]); // like toEqual, also checks undefined properties
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(0.1 + 0.2).toBeCloseTo(0.3); // floating-point safe!
expect(5).toBeGreaterThan(3);
expect(5).toBeLessThanOrEqual(5);
// Strings and arrays
expect("hello world").toContain("world");
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toHaveLength(3);
// Objects
expect(obj).toHaveProperty("name");
expect(obj).toHaveProperty("address.city", "Mumbai");
// Errors
expect(() => badFn()).toThrow();
expect(() => badFn()).toThrow(TypeError);
expect(() => badFn()).toThrow("specific message");Setup and teardown
import { beforeEach, afterEach, describe, test, expect } from "vitest";
describe("user store", () => {
let store;
beforeEach(() => {
// Runs before each test — set up fresh state:
store = new UserStore();
store.add({ id: 1, name: "Alice" });
});
afterEach(() => {
// Runs after each test — clean up:
store.clear();
});
test("findById returns the user", () => {
expect(store.findById(1).name).toBe("Alice");
});
test("findById returns null for missing id", () => {
expect(store.findById(99)).toBeNull();
});
});Official Sources
- Vitest Documentation
- Jest Documentation
- Testing Library — Guiding Principles
- MDN Web Docs — Test your JavaScript
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.