Skip to main content

Jest Testing Guide

The definitive reference for Jest — JavaScript's most-used testing framework. Covers matchers, mocking, snapshots, async patterns, coverage configuration, and TypeScript integration.

✓ Zero-config setup ✓ Built-in mocking ✓ Snapshot testing ✓ Code coverage ✓ TypeScript support

What is Jest?

Jest is a delightful JavaScript testing framework created by Meta and open-sourced to the community. Unlike older frameworks that required assembling a separate test runner (Mocha), assertion library (Chai), and mocking library (Sinon), Jest ships all three in a single, coherent package. This zero-configuration philosophy — combined with excellent watch mode, parallel test execution, and built-in code coverage — made Jest the dominant choice for React, Node.js, and TypeScript projects alike.

Jest uses jsdom as a simulated browser environment by default, making it ideal for testing React components without a real browser. For Node.js-only code, you can set testEnvironment: 'node' for better performance. With over 40 million weekly npm downloads, Jest has shaped how an entire generation of JavaScript developers thinks about testing.

Installation & Basic Setup

# Install Jest
npm install --save-dev jest

# With TypeScript support
npm install --save-dev jest ts-jest @types/jest typescript

# jest.config.ts
export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  coverageDirectory: 'coverage',
  collectCoverageFrom: ['src/**/*.ts'],
};

Core Test Structure: describe / it / expect

Jest tests are organised into suites (describe blocks) and individual test cases (it or test). The expect() function chains to matchers like toBe, toEqual, toThrow, and many more.

// math.test.ts
import { add, divide } from './math';

describe('add()', () => {
  it('returns the sum of two positive numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  it('handles negative numbers', () => {
    expect(add(-1, 1)).toBe(0);
  });
});

describe('divide()', () => {
  it('divides correctly', () => {
    expect(divide(10, 2)).toBeCloseTo(5);
  });

  it('throws on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
  });
});

Lifecycle Hooks: beforeEach / afterEach / beforeAll / afterAll

describe('DatabaseService', () => {
  let db: DatabaseService;

  beforeAll(async () => {
    // Runs once before all tests in this describe block
    await setupTestDatabase();
  });

  beforeEach(() => {
    // Runs before each individual test
    db = new DatabaseService({ url: 'sqlite::memory:' });
  });

  afterEach(async () => {
    // Clean up state between tests
    await db.clearAll();
    jest.clearAllMocks();
  });

  afterAll(async () => {
    // Runs once after all tests complete
    await teardownTestDatabase();
  });

  it('inserts a record', async () => {
    const id = await db.insert({ name: 'Alice' });
    expect(id).toBeDefined();
  });
});

Mocking: jest.fn(), jest.spyOn(), jest.mock()

// 1. Standalone mock function
const mockFetch = jest.fn().mockResolvedValue({ json: () => ({ id: 1 }) });

// 2. Spy on an existing method (original still runs)
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

// 3. Mock an entire module
jest.mock('./emailService', () => ({
  sendEmail: jest.fn().mockResolvedValue({ messageId: 'abc123' }),
}));

import { sendEmail } from './emailService';

it('calls sendEmail with correct args', async () => {
  await notifyUser('[email protected]', 'Hello');
  expect(sendEmail).toHaveBeenCalledWith('[email protected]', expect.stringContaining('Hello'));
  expect(sendEmail).toHaveBeenCalledTimes(1);
});

Async Testing

// Async/await (preferred)
it('fetches user data', async () => {
  const user = await fetchUser(42);
  expect(user).toMatchObject({ id: 42, name: expect.any(String) });
});

// Testing rejection
it('throws on 404', async () => {
  await expect(fetchUser(9999)).rejects.toThrow('User not found');
});

// Using resolves/rejects matchers
it('resolves to a valid token', () => {
  return expect(generateToken()).resolves.toMatch(/^[A-Za-z0-9._-]+$/);
});

Key Features

Use Cases

Comparison: Jest vs Alternatives

Feature Jest Vitest Jasmine Mocha
Zero config Yes Yes (Vite) Yes No
Built-in mocking Yes Yes Spies only No (needs Sinon)
Snapshot testing Yes Yes No No
Native ESM Partial Full Partial Full
Speed (large suites) Moderate Fast Moderate Moderate
React ecosystem fit Excellent Excellent Good Good

Pros & Cons

Pros

  • All-in-one: runner + assertions + mocks + coverage
  • Massive ecosystem and community documentation
  • Excellent React Testing Library integration
  • Snapshot testing catches regressions automatically
  • Parallel worker isolation prevents test bleed
  • Great watch mode with interactive filter UI

Cons

  • Slow startup for large TypeScript projects
  • Native ESM support is incomplete and tricky
  • Heavy memory footprint with many workers
  • jsdom doesn't perfectly replicate real browsers
  • Config can become complex in monorepos
  • Vitest is faster for Vite-based projects

Frequently Asked Questions

What is Jest and why is it the most popular JS testing framework?▼

Jest is a JavaScript testing framework developed by Meta (Facebook) and maintained by the open-source community. It became the dominant choice because it works with zero configuration for most projects, bundles mocking, assertion, and coverage tooling together, and integrates seamlessly with Babel, TypeScript, and React. Its snapshot testing feature and watch mode transformed developer workflow by making TDD fast and ergonomic.

What is the difference between describe, it, and test in Jest?▼

describe() groups related tests into a suite — it is optional but improves readability. it() and test() are aliases; both define a single test case. The convention is to use it() with a behaviour-description ('it should return 0 for empty input') and test() for more imperative descriptions ('test calculateTotal'). Nesting describe blocks lets you organize tests by feature, component, and scenario.

How does Jest snapshot testing work?▼

When you call expect(value).toMatchSnapshot(), Jest serializes the value and saves it to a __snapshots__ directory on the first run. On subsequent runs it compares the serialized output against the saved snapshot and fails if they differ. Snapshots are powerful for UI component output (React) and large JSON structures. Run jest --updateSnapshot (or jest -u) to regenerate snapshots after intentional changes.

What is the difference between jest.fn(), jest.spyOn(), and jest.mock()?▼

jest.fn() creates a standalone mock function with no implementation. jest.spyOn(object, 'method') wraps an existing method so you can track calls without fully replacing it — the original implementation still runs unless you chain .mockImplementation(). jest.mock('module') replaces an entire module with an auto-mocked version at the module system level, affecting all imports in the test file. Each serves a different granularity of isolation.

How do I test asynchronous code in Jest?▼

Jest supports three async patterns: (1) return a Promise from the test function and Jest waits for it to resolve; (2) use async/await which is the modern preferred style; (3) use the done callback for callback-style APIs. For promises, use expect(promise).resolves.toBe(value) or expect(promise).rejects.toThrow(). Always use expect.assertions(n) in async tests to ensure assertions actually ran if you are testing rejection paths.

How do I configure Jest for TypeScript?▼

Install ts-jest and @types/jest, then set transform: { '^.+\.tsx?$': 'ts-jest' } in jest.config.ts. Alternatively, use Babel with @babel/preset-typescript for faster (but type-unsafe) transforms. ts-jest runs the TypeScript compiler so type errors surface in tests; the Babel approach is faster but skips type-checking. For modern projects, Vitest is often preferred over Jest+TypeScript due to simpler config.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →