← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / Testing in JavaScript

Testing in JavaScript

Unit tests, integration tests, mocking, and the tools — Jest, Vitest, and the testing-library approach explained plainly.

What it is, in plain English: Testing in JavaScript means writing code that automatically checks whether your code works. A unit test checks one function in isolation. An integration test checks multiple pieces working together. Jest and Vitest are the most popular test runners — they find test files, run them, and report which assertions passed or failed. The testing-library approach tests what users see and do rather than implementation details.
Where this bites you

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();
  });
});
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.