Jest Cheatsheet
Comprehensive quick reference for Jest testing framework including matchers, mocking, async testing, configuration, and best practices for JavaScript/TypeScript projects.
Table of Contents
- Prerequisites
- Installation & Setup
- Basic Testing
- Matchers
- Async Testing
- Mocking
- Setup & Teardown
- Configuration
- Best Practices
- Common Pitfalls
Prerequisites
Jest Version: This cheatsheet targets Jest 29+. Some features require specific versions (noted inline).
Node.js: Jest 29+ requires Node.js 14.17+ or Node.js 16+ for optimal performance.
TypeScript Support: Use @types/jest and ts-jest or ts-node for TypeScript projects.
Installation & Setup
Initial Setup 🔧
# Install Jest and TypeScript typespnpm add -D jest @types/jest
# Install TypeScript support (optional)pnpm add -D ts-jest typescript
# Initialize Jest confignpx jest --init# → Creates jest.config.js with interactive prompts
# Run testspnpm test# orpnpm jest
# Run tests in watch modepnpm test --watch# orpnpm jest --watch
# Run tests with coveragepnpm test --coverage# orpnpm jest --coveragePackage.json Scripts 📝
{ "scripts": { "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "test:ci": "jest --ci --coverage --maxWorkers=2" }}Basic Configuration ⚙️
module.exports = { // Test environment testEnvironment: "node", // 'node' | 'jsdom' | 'jest-environment-jsdom'
// File extensions to test moduleFileExtensions: ["js", "jsx", "ts", "tsx", "json"],
// Transform files transform: { "^.+\\.tsx?$": "ts-jest", "^.+\\.jsx?$": "babel-jest", },
// Test match patterns testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
// Coverage settings collectCoverageFrom: [ "src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts", "!src/**/*.stories.{js,jsx,ts,tsx}", ],
// Module name mapping moduleNameMapper: { "^@/(.*)$": "<rootDir>/src/$1", },
// Setup files setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],};Basic Testing
Test Structure 📋
// Basic testtest("adds 1 + 2 to equal 3", () => { expect(1 + 2).toBe(3);});
// Using describe blocks for groupingdescribe("Math operations", () => { test("addition", () => { expect(2 + 2).toBe(4); });
test("subtraction", () => { expect(5 - 3).toBe(2); });
// Nested describe blocks describe("multiplication", () => { test("positive numbers", () => { expect(3 * 4).toBe(12); });
test("negative numbers", () => { expect(-2 * 3).toBe(-6); }); });});
// Using it() aliasit("should return true", () => { expect(true).toBe(true);});
// Test with async functiontest("async test", async () => { const result = await Promise.resolve(42); expect(result).toBe(42);});Test Naming Conventions 📝
// ✅ Good: Descriptive test namestest("should return user object when valid ID is provided", () => { // ...});
test("throws error when user ID is invalid", () => { // ...});
// ❌ Bad: Vague test namestest("test user", () => { // ...});
test("works", () => { // ...});Skipping Tests ⏭️
// Skip a testtest.skip("this test is skipped", () => { expect(true).toBe(false);});
// Skip with describedescribe.skip("skipped suite", () => { test("this will not run", () => { // ... });});
// Conditional skiptest("only runs in CI", () => { if (!process.env.CI) { test.skip("skipped locally"); } // ...});
// Run only this testtest.only("only this test runs", () => { expect(true).toBe(true);});Matchers
Equality Matchers ⚖️
// toBe() - strict equality (===)test("strict equality", () => { expect(2 + 2).toBe(4); expect("hello").toBe("hello"); expect({}).not.toBe({}); // Different object references});
// toEqual() - deep equalitytest("deep equality", () => { expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 }); expect([1, 2, 3]).toEqual([1, 2, 3]);
const obj = { name: "John" }; expect(obj).toEqual({ name: "John" });});
// toStrictEqual() - strict deep equality (checks undefined, type)test("strict deep equality", () => { expect({ a: 1 }).toStrictEqual({ a: 1 }); expect({ a: 1, b: undefined }).not.toStrictEqual({ a: 1 });});Truthiness Matchers ✅
// toBeTruthy() / toBeFalsy()test("truthiness", () => { expect(true).toBeTruthy(); expect(1).toBeTruthy(); expect("hello").toBeTruthy(); expect({}).toBeTruthy();
expect(false).toBeFalsy(); expect(0).toBeFalsy(); expect("").toBeFalsy(); expect(null).toBeFalsy(); expect(undefined).toBeFalsy();});
// toBeNull() / toBeUndefined() / toBeDefined()test("null and undefined", () => { expect(null).toBeNull(); expect(undefined).toBeUndefined(); expect("value").toBeDefined(); expect(null).toBeDefined(); // null is defined});Number Matchers 🔢
// toBeGreaterThan() / toBeGreaterThanOrEqual()test("number comparisons", () => { expect(10).toBeGreaterThan(5); expect(10).toBeGreaterThanOrEqual(10); expect(5).toBeLessThan(10); expect(5).toBeLessThanOrEqual(5);
// Floating point equality expect(0.1 + 0.2).toBeCloseTo(0.3, 5); expect(0.1 + 0.2).not.toBe(0.3); // Floating point precision issue});String Matchers 📝
// toMatch() - regex or substringtest("string matching", () => { expect("Hello World").toMatch("World"); expect("Hello World").toMatch(/World/); expect("Hello World").toMatch(/^Hello/); expect("test@example.com").toMatch(/^[\w-]+@[\w-]+\.[\w-]+$/);
// Case insensitive expect("Hello").toMatch(/hello/i);});
// toContain() - substring or array elementtest("containment", () => { expect("Hello World").toContain("World"); expect(["apple", "banana", "cherry"]).toContain("banana");});Array Matchers 📋
// toContain() / toContainEqual()test("array matchers", () => { const fruits = ["apple", "banana", "cherry"];
expect(fruits).toContain("banana"); expect(fruits).toHaveLength(3);
// Deep equality in arrays const users = [ { id: 1, name: "John" }, { id: 2, name: "Jane" }, ]; expect(users).toContainEqual({ id: 1, name: "John" });});
// toHaveLength()test("array length", () => { expect([1, 2, 3]).toHaveLength(3); expect("hello").toHaveLength(5);});Object Matchers 🎯
// toHaveProperty()test("object properties", () => { const user = { name: "John", age: 30, email: "john@example.com" };
expect(user).toHaveProperty("name"); expect(user).toHaveProperty("age", 30); expect(user).toHaveProperty("address", undefined);
// Nested properties const nested = { user: { profile: { name: "John" } } }; expect(nested).toHaveProperty("user.profile.name", "John");});
// toMatchObject() - partial matchtest("partial object match", () => { const user = { name: "John", age: 30, email: "john@example.com" };
expect(user).toMatchObject({ name: "John", age: 30 }); expect(user).toMatchObject({ name: "John" });});Exception Matchers ⚠️
// toThrow() - check if function throwstest("exception matchers", () => { // Throw any error expect(() => { throw new Error("Something went wrong"); }).toThrow();
// Throw specific error message expect(() => { throw new Error("Something went wrong"); }).toThrow("Something went wrong");
// Throw error matching regex expect(() => { throw new Error("Invalid input"); }).toThrow(/Invalid/);
// Throw specific error type expect(() => { throw new TypeError("Type error"); }).toThrow(TypeError);
// Not throw expect(() => { return 42; }).not.toThrow();});
// Function that throwsfunction divide(a, b) { if (b === 0) { throw new Error("Division by zero"); } return a / b;}
test("division by zero", () => { expect(() => divide(10, 0)).toThrow("Division by zero"); expect(() => divide(10, 2)).not.toThrow(); expect(divide(10, 2)).toBe(5);});Mock Matchers 🎭
// toHaveBeenCalled() / toHaveBeenCalledTimes()test("mock call matchers", () => { const mockFn = jest.fn();
mockFn(); mockFn("arg1", "arg2");
expect(mockFn).toHaveBeenCalled(); expect(mockFn).toHaveBeenCalledTimes(2); expect(mockFn).toHaveBeenCalledWith("arg1", "arg2"); expect(mockFn).toHaveBeenLastCalledWith("arg1", "arg2"); expect(mockFn).toHaveBeenNthCalledWith(1); // First call expect(mockFn).toHaveBeenNthCalledWith(2, "arg1", "arg2"); // Second call});
// toHaveReturned() / toHaveReturnedWith()test("mock return matchers", () => { const mockFn = jest .fn() .mockReturnValueOnce(10) .mockReturnValueOnce(20) .mockReturnValue(30);
expect(mockFn()).toBe(10); expect(mockFn()).toBe(20); expect(mockFn()).toBe(30);
expect(mockFn).toHaveReturned(); expect(mockFn).toHaveReturnedWith(10); expect(mockFn).toHaveReturnedTimes(3);});Custom Matchers 🎨
// Extend Jest matchersexpect.extend({ toBeWithinRange(received, floor, ceiling) { const pass = received >= floor && received <= ceiling; if (pass) { return { message: () => `expected ${received} not to be within range ${floor} - ${ceiling}`, pass: true, }; } else { return { message: () => `expected ${received} to be within range ${floor} - ${ceiling}`, pass: false, }; } },});
test("custom matcher", () => { expect(5).toBeWithinRange(1, 10); expect(15).not.toBeWithinRange(1, 10);});Async Testing
Promises 🔄
// Return promisetest("promise resolves", () => { return fetchData().then((data) => { expect(data).toBe("data"); });});
// Using async/awaittest("async/await", async () => { const data = await fetchData(); expect(data).toBe("data");});
// Promise rejectiontest("promise rejects", async () => { await expect(fetchDataError()).rejects.toThrow("Error message");
// Or with try/catch try { await fetchDataError(); fail("should have thrown"); } catch (error) { expect(error.message).toBe("Error message"); }});
// Promise resolutiontest("promise resolves with value", async () => { await expect(Promise.resolve("success")).resolves.toBe("success");});Async Functions Examples 🌍
// Fetch APIasync function fetchUser(id) { const response = await fetch(`/api/users/${id}`); if (!response.ok) { throw new Error("Failed to fetch user"); } return response.json();}
test("fetches user data", async () => { global.fetch = jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ id: 1, name: "John" }), }), );
const user = await fetchUser(1); expect(user).toEqual({ id: 1, name: "John" }); expect(fetch).toHaveBeenCalledWith("/api/users/1");});
// Multiple async operationstest("multiple async operations", async () => { const [user, posts] = await Promise.all([fetchUser(1), fetchPosts(1)]);
expect(user).toBeDefined(); expect(posts).toHaveLength(5);});Timers ⏱️
// Fake timersjest.useFakeTimers();
test("timer test", () => { const callback = jest.fn();
setTimeout(callback, 1000);
expect(callback).not.toHaveBeenCalled();
// Fast-forward time jest.advanceTimersByTime(1000); expect(callback).toHaveBeenCalledTimes(1);
// Run all pending timers jest.runAllTimers();
// Run only pending timers jest.runOnlyPendingTimers();
// Clear all timers jest.clearAllTimers();});
// Restore real timersafterEach(() => { jest.useRealTimers();});
// setInterval exampletest("interval test", () => { const callback = jest.fn();
setInterval(callback, 100);
jest.advanceTimersByTime(500); expect(callback).toHaveBeenCalledTimes(5);});Mocking
Function Mocks 🎭
// jest.fn() - create mock functiontest("mock function", () => { const mockFn = jest.fn();
mockFn("arg1", "arg2");
expect(mockFn).toHaveBeenCalledWith("arg1", "arg2"); expect(mockFn).toHaveBeenCalledTimes(1);});
// Mock return valuestest("mock return value", () => { const mockFn = jest.fn();
mockFn.mockReturnValue(42); expect(mockFn()).toBe(42);
// Multiple return values mockFn.mockReturnValueOnce(10).mockReturnValueOnce(20).mockReturnValue(30);
expect(mockFn()).toBe(10); expect(mockFn()).toBe(20); expect(mockFn()).toBe(30); expect(mockFn()).toBe(30);});
// Mock implementationtest("mock implementation", () => { const mockFn = jest.fn((a, b) => a + b);
expect(mockFn(2, 3)).toBe(5);
// Change implementation mockFn.mockImplementation((a, b) => a * b); expect(mockFn(2, 3)).toBe(6);});
// Mock async functionstest("mock async function", async () => { const mockAsyncFn = jest.fn().mockResolvedValue("success");
const result = await mockAsyncFn(); expect(result).toBe("success");
// Mock rejection const mockRejectFn = jest.fn().mockRejectedValue(new Error("failed")); await expect(mockRejectFn()).rejects.toThrow("failed");});Module Mocks 📦
// jest.mock() - mock entire modulejest.mock("./api");
import { fetchData } from "./api";
test("mocked module", async () => { fetchData.mockResolvedValue({ data: "test" });
const result = await fetchData(); expect(result).toEqual({ data: "test" });});
// Manual mock implementationjest.mock("./utils", () => ({ ...jest.requireActual("./utils"), helperFunction: jest.fn(() => "mocked"),}));
// Mock with factoryjest.mock("./api", () => { return { fetchData: jest.fn(), postData: jest.fn(), };});
// Partial mock (keep some real implementations)jest.mock("./utils", () => ({ ...jest.requireActual("./utils"), expensiveFunction: jest.fn(),}));Spy Functions 👁️
// jest.spyOn() - spy on object methodtest("spy on method", () => { const obj = { method: () => "original", };
const spy = jest.spyOn(obj, "method");
obj.method(); expect(spy).toHaveBeenCalled();
// Mock implementation spy.mockReturnValue("mocked"); expect(obj.method()).toBe("mocked");
// Restore original spy.mockRestore(); expect(obj.method()).toBe("original");});
// Spy on class methodclass Calculator { add(a, b) { return a + b; }}
test("spy on class method", () => { const calc = new Calculator(); const spy = jest.spyOn(calc, "add");
calc.add(2, 3); expect(spy).toHaveBeenCalledWith(2, 3);
spy.mockReturnValue(10); expect(calc.add(2, 3)).toBe(10);});Mock Files 📁
// __mocks__/api.js - manual mock file// Place in __mocks__ directory next to module
export const fetchData = jest.fn(() => Promise.resolve({ data: "mocked" }));export const postData = jest.fn(() => Promise.resolve({ success: true }));
// Use manual mockjest.mock("./api");
// Clear mocks between testsbeforeEach(() => { jest.clearAllMocks();});Common Mocking Patterns 🎯
// Mock fetchglobal.fetch = jest.fn();
test("mock fetch", async () => { fetch.mockResolvedValueOnce({ ok: true, json: async () => ({ data: "test" }), });
const response = await fetch("/api/data"); const data = await response.json();
expect(data).toEqual({ data: "test" });});
// Mock localStorageconst localStorageMock = { getItem: jest.fn(), setItem: jest.fn(), removeItem: jest.fn(), clear: jest.fn(),};global.localStorage = localStorageMock;
// Mock Dateconst mockDate = new Date("2024-01-01");jest.spyOn(global, "Date").mockImplementation(() => mockDate);
// Mock Math.randomjest.spyOn(Math, "random").mockReturnValue(0.5);Setup & Teardown
Global Setup 🔧
// beforeEach() - runs before each testbeforeEach(() => { // Setup code initializeDatabase();});
// afterEach() - runs after each testafterEach(() => { // Cleanup code cleanupDatabase(); jest.clearAllMocks();});
// beforeAll() - runs once before all testsbeforeAll(async () => { await setupTestDatabase();});
// afterAll() - runs once after all testsafterAll(async () => { await teardownTestDatabase();});Scoped Setup 📦
describe("User service", () => { let userService;
beforeEach(() => { userService = new UserService(); });
afterEach(() => { userService.cleanup(); });
test("creates user", () => { const user = userService.create({ name: "John" }); expect(user).toBeDefined(); });
test("deletes user", () => { const user = userService.create({ name: "John" }); userService.delete(user.id); expect(userService.findById(user.id)).toBeUndefined(); });});Setup Files 📝
// setupTests.ts or setupTests.jsimport "@testing-library/jest-dom";
// Global test utilitiesglobal.testUtils = { createMockUser: () => ({ id: 1, name: "Test User" }),};
// Mock console methods to reduce noiseglobal.console = { ...console, log: jest.fn(), debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(),};
// jest.config.jsmodule.exports = { setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],};Configuration
Jest Config Options ⚙️
module.exports = { // Test environment testEnvironment: "node", // 'node' | 'jsdom'
// Coverage settings collectCoverage: true, coverageDirectory: "coverage", coverageReporters: ["text", "lcov", "html"], coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, },
// Test patterns testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
// Ignore patterns testPathIgnorePatterns: ["/node_modules/", "/dist/", "/build/"],
// Module resolution moduleNameMapper: { "^@/(.*)$": "<rootDir>/src/$1", "\\.(css|less|scss|sass)$": "identity-obj-proxy", },
// Transform transform: { "^.+\\.tsx?$": "ts-jest", "^.+\\.jsx?$": "babel-jest", },
// Setup files setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
// Verbose output verbose: true,
// Max workers (parallelization) maxWorkers: "50%",
// Clear mocks clearMocks: true, resetMocks: true, restoreMocks: true,};TypeScript Configuration 🔷
// jest.config.js with ts-jestmodule.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['<rootDir>/src'], testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], transform: { '^.+\\.ts$': 'ts-jest', }, collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', ], moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1', },};
// tsconfig.json for tests{ "compilerOptions": { "types": ["jest", "@types/jest"], "esModuleInterop": true, }}Environment Variables 🔐
DATABASE_URL=postgresql://test:test@localhost:5432/testdbAPI_KEY=test-key
// jest.config.jsmodule.exports = { setupFiles: ['<rootDir>/jest.setup.js'],};
// jest.setup.jsrequire('dotenv').config({ path: '.env.test' });Best Practices
Test Organization ✅
// ✅ Good: Group related testsdescribe("UserService", () => { describe("createUser", () => { test("creates user with valid data", () => { // ... });
test("throws error with invalid email", () => { // ... }); });
describe("deleteUser", () => { test("deletes existing user", () => { // ... }); });});
// ✅ Good: One assertion per test (when possible)test("user has required fields", () => { const user = createUser({ name: "John", email: "john@example.com" }); expect(user).toHaveProperty("id"); expect(user).toHaveProperty("name"); expect(user).toHaveProperty("email");});
// ✅ Good: Descriptive test namestest("throws ValidationError when email is missing", () => { // ...});Test Isolation ✅
// ✅ Good: Each test is independentdescribe("Calculator", () => { let calculator;
beforeEach(() => { calculator = new Calculator(); // Fresh instance for each test });
test("adds numbers", () => { expect(calculator.add(2, 3)).toBe(5); });
test("subtracts numbers", () => { expect(calculator.subtract(5, 3)).toBe(2); });});
// ❌ Bad: Tests depend on each otherlet sharedState = 0;
test("increments shared state", () => { sharedState++; expect(sharedState).toBe(1);});
test("uses shared state", () => { expect(sharedState).toBe(1); // Depends on previous test!});Mocking Best Practices ✅
// ✅ Good: Clear mock setupdescribe("UserService", () => { let mockApi;
beforeEach(() => { mockApi = { fetchUser: jest.fn(), createUser: jest.fn(), }; });
afterEach(() => { jest.clearAllMocks(); });
test("fetches user", async () => { mockApi.fetchUser.mockResolvedValue({ id: 1, name: "John" }); // ... });});
// ✅ Good: Mock only what you needjest.mock("./api", () => ({ fetchData: jest.fn(), // Don't mock everything, only what's needed}));
// ❌ Bad: Over-mockingjest.mock("./api"); // Mocks everything unnecessarilyAsync Testing Best Practices ✅
// ✅ Good: Always await async operationstest("fetches data", async () => { const data = await fetchData(); expect(data).toBeDefined();});
// ✅ Good: Test both success and error casestest("handles fetch error", async () => { fetch.mockRejectedValueOnce(new Error("Network error")); await expect(fetchData()).rejects.toThrow("Network error");});
// ❌ Bad: Missing awaittest("fetches data", () => { const data = fetchData(); // Missing await! expect(data).toBeDefined(); // This will fail});Common Pitfalls
Async/Await Issues ⚠️
Warning: Always use
awaitwith async operations or return promises from tests.
// ❌ Wrong: Missing awaittest("async test", () => { const result = fetchData(); // Returns Promise, not data expect(result).toBe("data"); // Fails: comparing Promise to string});
// ✅ Correct: Use awaittest("async test", async () => { const result = await fetchData(); expect(result).toBe("data");});
// ✅ Alternative: Return promisetest("async test", () => { return fetchData().then((result) => { expect(result).toBe("data"); });});Mock Not Restored ⚠️
// ❌ Wrong: Mock affects other teststest("test 1", () => { jest.spyOn(Math, "random").mockReturnValue(0.5); // Test code // Mock not restored!});
test("test 2", () => { // Math.random still mocked from test 1! expect(Math.random()).toBe(0.5); // Unexpected behavior});
// ✅ Correct: Restore mocksafterEach(() => { jest.restoreAllMocks();});
// Or manuallytest("test 1", () => { const spy = jest.spyOn(Math, "random").mockReturnValue(0.5); // Test code spy.mockRestore();});Floating Point Comparisons ⚠️
// ❌ Wrong: Direct equality with floatstest("floating point", () => { expect(0.1 + 0.2).toBe(0.3); // Fails due to precision});
// ✅ Correct: Use toBeCloseTo()test("floating point", () => { expect(0.1 + 0.2).toBeCloseTo(0.3, 5);});Test Timeout Issues ⚠️
// Increase timeout for slow teststest("slow operation", async () => { const result = await slowOperation(); expect(result).toBeDefined();}, 10000); // 10 second timeout
// Or set globallyjest.setTimeout(10000);Object Reference Equality ⚠️
// ❌ Wrong: Using toBe() for object comparisontest("object equality", () => { expect({ a: 1 }).toBe({ a: 1 }); // Fails: different references});
// ✅ Correct: Use toEqual() for deep equalitytest("object equality", () => { expect({ a: 1 }).toEqual({ a: 1 }); // Passes: deep comparison});Mock Implementation Not Called ⚠️
// ❌ Wrong: Mock not set up before usetest("uses mock", () => { const result = fetchData(); // Called before mock setup jest.mock("./api", () => ({ fetchData: jest.fn(() => "mocked"), })); expect(result).toBe("mocked"); // Fails});
// ✅ Correct: Set up mocks before usejest.mock("./api", () => ({ fetchData: jest.fn(() => "mocked"),}));
test("uses mock", () => { const result = fetchData(); expect(result).toBe("mocked"); // Passes});