Toolmingo
Guides8 min read

Jest Cheat Sheet: Unit Testing JavaScript & TypeScript

A complete Jest cheat sheet — matchers, mocks, spies, async tests, coverage, and TypeScript setup. Copy-ready patterns for unit and integration testing in JS/TS.

Jest is the most widely used JavaScript testing framework. It works with Vanilla JS, TypeScript, React, Node.js, and most modern JS toolchains with zero configuration. This cheat sheet covers every Jest feature you need to write reliable tests.

Quick reference

Task Jest API
Define a test suite describe('Name', () => { ... })
Define a test it('does X', () => { ... }) / test(...)
Assert equality expect(val).toBe(expected)
Deep equality expect(obj).toEqual({ a: 1 })
Negate matcher expect(val).not.toBe(x)
Mock a function jest.fn()
Mock a module jest.mock('./module')
Spy on a method jest.spyOn(obj, 'method')
Run before each test beforeEach(() => { ... })
Run after all tests afterAll(() => { ... })
Skip a test it.skip(...) / xit(...)
Focus one test it.only(...) / fit(...)
Async test it('...', async () => { await ... })
Assert throws expect(() => fn()).toThrow()
Snapshot expect(component).toMatchSnapshot()
Coverage jest --coverage

Installation and setup

# New project
npm install --save-dev jest

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

# React / jsdom environment
npm install --save-dev jest jest-environment-jsdom @testing-library/react

jest.config.ts (TypeScript):

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',       // or 'jsdom' for browser code
  clearMocks: true,              // reset mocks between tests
  coverageThreshold: {
    global: { lines: 80 },
  },
};

export default config;

package.json scripts:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --forceExit"
  }
}

Test structure

describe('Calculator', () => {
  // Runs once before all tests in this suite
  beforeAll(() => {
    // expensive setup (DB connection, server start)
  });

  // Runs before each test
  beforeEach(() => {
    // reset state, clear mocks
  });

  afterEach(() => {
    // cleanup per test
  });

  afterAll(() => {
    // teardown (close DB, stop server)
  });

  it('adds two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

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

Nested suites

describe('UserService', () => {
  describe('create()', () => {
    it('returns a new user', () => { ... });
    it('throws if email exists', () => { ... });
  });

  describe('delete()', () => {
    it('removes the user', () => { ... });
  });
});

Matchers

Equality

expect(2 + 2).toBe(4);                  // strict ===
expect({ a: 1 }).toEqual({ a: 1 });     // deep equal (objects/arrays)
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 }); // partial match
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('hello').toBeDefined();
expect(true).toBeTruthy();
expect(0).toBeFalsy();

Numbers

expect(5).toBeGreaterThan(3);
expect(5).toBeGreaterThanOrEqual(5);
expect(3).toBeLessThan(5);
expect(3).toBeLessThanOrEqual(3);
expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // floating point

Strings

expect('hello world').toContain('world');
expect('hello').toMatch(/^hel/);
expect('hello').toMatch('ell');

Arrays and iterables

expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toHaveLength(3);
expect([{ id: 1 }, { id: 2 }]).toContainEqual({ id: 1 }); // deep
expect(new Set([1, 2])).toEqual(new Set([1, 2]));

Errors

expect(() => JSON.parse('bad')).toThrow();
expect(() => JSON.parse('bad')).toThrow(SyntaxError);
expect(() => fn()).toThrow('expected message');
expect(() => fn()).toThrow(/pattern/);

Negation

expect(null).not.toBeDefined();
expect([1, 2]).not.toContain(3);
expect(() => safeOp()).not.toThrow();

Mock functions (jest.fn)

A mock function records every call — arguments, return values, and this context.

const mockFn = jest.fn();

mockFn(1, 2);
mockFn('hello');

expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith(1, 2);
expect(mockFn).toHaveBeenLastCalledWith('hello');
expect(mockFn).toHaveBeenNthCalledWith(1, 1, 2); // first call

Controlling return values

const fn = jest.fn();

fn.mockReturnValue('default');
fn.mockReturnValueOnce('first').mockReturnValueOnce('second');

fn.mockResolvedValue({ data: [] });          // resolves a Promise
fn.mockRejectedValue(new Error('fail'));     // rejects a Promise
fn.mockResolvedValueOnce({ data: [1] });     // one-time resolved value

fn.mockImplementation((x: number) => x * 2);
fn.mockImplementationOnce(() => 42);

Inspecting calls

console.log(mockFn.mock.calls);      // [[1,2], ['hello']]
console.log(mockFn.mock.results);    // [{ type:'return', value:... }, ...]
console.log(mockFn.mock.instances);  // `this` for each call

Reset and restore

mockFn.mockClear();    // clears calls/results, keeps implementation
mockFn.mockReset();    // also removes implementation
mockFn.mockRestore();  // only works for spies (restores original)

Spying on existing methods

const user = {
  getName() { return 'Alice'; },
};

const spy = jest.spyOn(user, 'getName');
spy.mockReturnValue('Bob');

expect(user.getName()).toBe('Bob');
expect(spy).toHaveBeenCalled();

spy.mockRestore(); // puts 'Alice' back

Spy on a module method:

import * as fs from 'fs';

jest.spyOn(fs, 'readFileSync').mockReturnValue('mocked content');

Mocking modules

Auto-mock entire module

jest.mock('./database');

// All exports are replaced with jest.fn()
import { getUser } from './database';
(getUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Alice' });

Manual mock with factory

jest.mock('./emailService', () => ({
  sendEmail: jest.fn().mockResolvedValue({ sent: true }),
  validateAddress: jest.fn().mockReturnValue(true),
}));

Partial mock (keep some real)

jest.mock('./utils', () => {
  const real = jest.requireActual('./utils');
  return {
    ...real,
    formatDate: jest.fn().mockReturnValue('2026-01-01'),
  };
});

Mock Node modules

jest.mock('axios', () => ({
  get: jest.fn().mockResolvedValue({ data: { id: 1 } }),
  post: jest.fn().mockResolvedValue({ data: { created: true } }),
}));

__mocks__ directory (automatic)

Place __mocks__/fs.ts next to node_modules/ and Jest picks it up automatically when jest.mock('fs') is called.


Async tests

async/await (preferred)

it('fetches user data', async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe('Alice');
});

Resolves / rejects matchers

it('resolves with data', async () => {
  await expect(fetchUser(1)).resolves.toEqual({ id: 1, name: 'Alice' });
});

it('rejects on error', async () => {
  await expect(fetchUser(-1)).rejects.toThrow('Not found');
});

Fake timers

beforeEach(() => { jest.useFakeTimers(); });
afterEach(() => { jest.useRealTimers(); });

it('calls callback after delay', () => {
  const cb = jest.fn();
  setTimeout(cb, 1000);

  expect(cb).not.toHaveBeenCalled();
  jest.advanceTimersByTime(1000);
  expect(cb).toHaveBeenCalledTimes(1);
});

it('runs all pending timers', () => {
  jest.runAllTimers();
});

Snapshots

Snapshots catch unexpected UI or data structure changes.

import { render } from '@testing-library/react';
import Button from './Button';

it('renders correctly', () => {
  const { container } = render(<Button label="Click me" />);
  expect(container).toMatchSnapshot();
});

Update snapshots when a change is intentional:

jest --updateSnapshot   # or: jest -u

Inline snapshots

expect({ name: 'Alice', role: 'admin' }).toMatchInlineSnapshot(`
  {
    "name": "Alice",
    "role": "admin",
  }
`);

Testing React components

npm install --save-dev @testing-library/react @testing-library/user-event
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';

describe('LoginForm', () => {
  it('shows error on empty submit', async () => {
    const user = userEvent.setup();
    render(<LoginForm onLogin={jest.fn()} />);

    await user.click(screen.getByRole('button', { name: /login/i }));

    expect(screen.getByText(/email is required/i)).toBeInTheDocument();
  });

  it('calls onLogin with credentials', async () => {
    const onLogin = jest.fn().mockResolvedValue(undefined);
    const user = userEvent.setup();
    render(<LoginForm onLogin={onLogin} />);

    await user.type(screen.getByLabelText(/email/i), 'alice@example.com');
    await user.type(screen.getByLabelText(/password/i), 'secret');
    await user.click(screen.getByRole('button', { name: /login/i }));

    expect(onLogin).toHaveBeenCalledWith({
      email: 'alice@example.com',
      password: 'secret',
    });
  });
});

Testing API handlers (Node.js)

import request from 'supertest';
import app from '../app';
import { db } from '../db';

jest.mock('../db');

describe('GET /users/:id', () => {
  it('returns 200 with user', async () => {
    (db.findUser as jest.Mock).mockResolvedValue({ id: '1', name: 'Alice' });

    const res = await request(app).get('/users/1');

    expect(res.status).toBe(200);
    expect(res.body).toEqual({ id: '1', name: 'Alice' });
  });

  it('returns 404 when not found', async () => {
    (db.findUser as jest.Mock).mockResolvedValue(null);

    const res = await request(app).get('/users/999');

    expect(res.status).toBe(404);
  });
});

Coverage

jest --coverage

Configure thresholds in jest.config.ts:

coverageThreshold: {
  global: {
    branches: 80,
    functions: 80,
    lines: 80,
    statements: 80,
  },
  './src/auth/': {
    lines: 95,    // stricter threshold for critical paths
  },
},
coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '*.d.ts'],

Coverage reporters:

coverageReporters: ['text', 'lcov', 'html'],

Common patterns

Factory helpers

function makeUser(overrides: Partial<User> = {}): User {
  return {
    id: '1',
    name: 'Alice',
    email: 'alice@example.com',
    role: 'user',
    ...overrides,
  };
}

it('admin can delete posts', () => {
  const admin = makeUser({ role: 'admin' });
  expect(canDelete(admin)).toBe(true);
});

Testing error boundaries

it('returns error for invalid input', async () => {
  const result = await processData(null);
  expect(result.success).toBe(false);
  expect(result.error).toMatch(/invalid/i);
});

Parameterized tests with test.each

test.each([
  [1, 1, 2],
  [0, 0, 0],
  [-1, 1, 0],
])('add(%i, %i) = %i', (a, b, expected) => {
  expect(add(a, b)).toBe(expected);
});

// With objects
test.each([
  { input: 'hello', expected: 'HELLO' },
  { input: '',      expected: '' },
])('toUpperCase("$input") = "$expected"', ({ input, expected }) => {
  expect(input.toUpperCase()).toBe(expected);
});

Common mistakes

Mistake Problem Fix
expect(promise) without await Test always passes — assertion never runs await expect(promise).resolves.toBe(...)
Forgetting return in promise test Same — unhandled rejection silently ignored Use async/await instead
toBe for objects/arrays {} !== {} in JS — reference check fails Use toEqual for deep equality
Mocking after import Jest hoists jest.mock() — mocking after import has no effect Always put jest.mock() at top level
Not clearing mocks between tests State leaks cause flaky tests Set clearMocks: true in config
jest.fn() inside describe without reset Mock accumulates calls across tests Use beforeEach to recreate or mockClear()
Snapshot tests without thinking Snapshots of large trees break on any change Snapshot only stable, minimal output

TypeScript tips

// Type the mock
const mockFetch = fetch as jest.MockedFunction<typeof fetch>;
mockFetch.mockResolvedValue(new Response(JSON.stringify({ id: 1 })));

// Mock a class
jest.mock('./UserRepository');
const MockRepo = UserRepository as jest.MockedClass<typeof UserRepository>;
MockRepo.prototype.findById.mockResolvedValue({ id: '1', name: 'Alice' });

// Assert mock calls with types
expect(mockFn).toHaveBeenCalledWith<[string, number]>('key', 42);

Frequently asked questions

What's the difference between describe, it, and test?
it and test are identical — choose whichever reads better ("it should..." vs "test that..."). describe groups related tests into a suite.

When should I use toBe vs toEqual?
Use toBe for primitives (string, number, boolean, null, undefined). Use toEqual for objects and arrays — it compares values recursively, not references.

How do I run a single test file?
jest path/to/file.test.ts or jest --testPathPattern="auth" to match by name pattern.

How do I debug a failing test?
Add --verbose to see each test name. Use console.log inside tests. Run node --inspect-brk node_modules/.bin/jest --runInBand and attach a debugger. VS Code has built-in Jest debugging via the "Jest Runner" extension.

What's --runInBand for?
It runs all tests serially in the main process (no worker threads). Useful for debugging and for tests that share global state like a real database.

How do I test code that uses Date.now() or Math.random()?
Mock them: jest.spyOn(Date, 'now').mockReturnValue(1700000000000) or jest.spyOn(Math, 'random').mockReturnValue(0.5). For Date, jest.useFakeTimers() also controls new Date().

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools