# Testing with Jest

Types describe valid inputs and outputs; tests check behavior. A function can return a number and still calculate the wrong number. Jest runs tests, reports failed assertions and provides tools for isolating dependencies.

Test level What it checks
Unit A small function or module in isolation
Integration Components working together, such as a route and data layer
End-to-end A user's flow through the running application

Start with fast, deterministic unit tests. The following examples use Jest 29/30, plain JavaScript, CommonJS and the Node test environment in an example application. Use "type": "commonjs" in its package manifest; no TypeScript transformer or browser environment is needed for these tests.

# Arrange, act, assert

Create calculator.js. Its contract accepts numeric operands and rejects division by zero:

function sum(a, b) {
  return a + b;
}

function divide(a, b) {
  if (b === 0) throw new RangeError('Division by zero');
  return a / b;
}

module.exports = { sum, divide };

Create calculator.test.js:

const { sum, divide } = require('./calculator');

describe('calculator', () => {
  test.each([
    [2, 5, 7],
    [0, 0, 0],
    [-2, 5, 3]
  ])('sum(%i, %i) is %i', (a, b, expected) => {
    const result = sum(a, b);
    expect(result).toBe(expected);
  });

  test('divides numbers', () => {
    expect(divide(6, 2)).toBe(3);
  });

  test('rejects a zero divisor', () => {
    expect(() => divide(6, 0)).toThrow('Division by zero');
  });
});

Arrange supplies inputs, act calls the operation, and assert checks the result. describe groups behavior, test names a case, and expect makes an assertion. Boundary cases and a specified failure are more useful than asserting that sum(2, 5) is not some unrelated number.

Matcher Use
toBe Primitive equality or object identity (Object.is)
toEqual Structural comparison of arrays/objects
toThrow A supplied function throws; wrap the call rather than invoking it immediately
resolves / rejects A Promise fulfills/rejects; await or return the assertion

For example, expect({ total: sum(2, 5) }).toEqual({ total: 7 }) compares content; toBe would compare two different object references.

# Async behavior and a small mock

In greeting.js, inject the data-loading function instead of making the unit test depend on a real network:

async function greetingFor(id, loadStudent) {
  const student = await loadStudent(id);
  return `Hello, ${student.name}`;
}

module.exports = { greetingFor };

In greeting.test.js:

const { greetingFor } = require('./greeting');

test('greets the loaded student', async () => {
  const loadStudent = jest.fn().mockResolvedValue({ name: 'Ada' });
  await expect(greetingFor(1, loadStudent)).resolves.toBe('Hello, Ada');
  expect(loadStudent).toHaveBeenCalledWith(1);
});

test('propagates a loading failure', async () => {
  const loadStudent = jest.fn().mockRejectedValue(new Error('Offline'));
  await expect(greetingFor(1, loadStudent)).rejects.toThrow('Offline');
});

jest.fn() records calls and can provide controlled results. Mock the external loader, not greetingFor itself: replacing the behavior under test would prove nothing. Each test creates its own mock, preventing call history from leaking between cases.

Without await or return, a test can finish before its Promise assertion runs. Avoid real network calls and arbitrary sleep delays in unit tests; control external results instead.

# Running tests

With Jest installed as a development dependency, add these scripts:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watchAll",
    "test:coverage": "jest --coverage"
  }
}

Run all tests with npm test, or focus on the calculator with npm test -- calculator.test.js. Watch mode repeats tests during editing. Coverage tells you which code executed, not whether the assertions detect all mistakes.

Jest's default Node environment has no DOM. Browser-element tests need a separately configured environment such as jest-environment-jsdom. TypeScript/ESM tests also need compatible Jest configuration; a Webpack ts-loader rule does not configure Jest. Run type checking separately if the chosen test transformer only transpiles.

Try it: introduce a mistake into sum and verify a test fails, then restore the implementation. Add a case for two negative numbers and explain what the existing tests did not cover.

Jest getting started (opens new window) · Async tests (opens new window) · Matchers (opens new window)