Jest is the most popular JavaScript testing framework. It comes with a test runner, assertion library, and mocking utilities — all in one package. Understanding its core API is essential before diving into TDD.
Setting Up Jest
Install Jest with TypeScript support:
npm install -D jest @types/jest ts-jest
npx ts-jest config:initThis creates a jest.config.js file. Add a test script to package.json:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch"
}
}Jest automatically finds files matching *.test.ts, *.test.tsx, or *.spec.ts.
test() and describe()
The test() function defines a single test case. The describe() function groups related tests:
describe('MathUtils', () => {
describe('add', () => {
test('adds two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('adds negative numbers', () => {
expect(add(-1, -2)).toBe(-3);
});
test('adds zero', () => {
expect(add(5, 0)).toBe(5);
});
});
});Groups can nest, creating a hierarchy that reads like a specification. The test output mirrors this structure, making it easy to locate failures.
Essential Matchers
Jest's expect() function returns an object with matchers — methods that test different conditions.
Equality
// Strict equality (===) — use for primitives
expect(2 + 2).toBe(4);
expect('hello').toBe('hello');
// Deep equality — use for objects and arrays
expect({ name: 'Alice', age: 30 }).toEqual({ name: 'Alice', age: 30 });
expect([1, 2, 3]).toEqual([1, 2, 3]);
// Partial match — object contains these properties
expect(user).toMatchObject({ name: 'Alice' });Truthiness
expect(value).toBeTruthy(); // value is truthy
expect(value).toBeFalsy(); // value is falsy
expect(value).toBeNull(); // value === null
expect(value).toBeUndefined(); // value === undefined
expect(value).toBeDefined(); // value !== undefined
Numbers
expect(price).toBeGreaterThan(0);
expect(price).toBeLessThanOrEqual(100);
expect(0.1 + 0.2).toBeCloseTo(0.3); // handles floating point
Strings
expect(message).toContain('success');
expect(email).toMatch(/^[\w.-]+@[\w.-]+\.\w+$/);Arrays and Iterables
expect(fruits).toContain('apple');
expect(results).toHaveLength(5);
expect(tags).toEqual(expect.arrayContaining(['js', 'react']));Exceptions
expect(() => divide(10, 0)).toThrow();
expect(() => divide(10, 0)).toThrow('Division by zero');
expect(() => divide(10, 0)).toThrow(ArithmeticError);Negation
Any matcher can be negated with .not:
expect(value).not.toBe(0);
expect(array).not.toContain('banana');Setup and Teardown Hooks
When multiple tests share setup logic, use hooks to avoid duplication:
describe('UserService', () => {
let db: Database;
beforeAll(async () => {
// Runs once before all tests in this describe block
db = await Database.connect();
});
afterAll(async () => {
// Runs once after all tests
await db.disconnect();
});
beforeEach(() => {
// Runs before each test
db.clear();
});
afterEach(() => {
// Runs after each test
jest.restoreAllMocks();
});
test('creates a user', async () => {
const user = await UserService.create(db, { name: 'Alice' });
expect(user.id).toBeDefined();
});
});Use beforeEach to ensure each test starts with a clean state. Use beforeAll for expensive setup that can be shared (like database connections).
Parameterized Tests with test.each
When you want to test the same logic with different inputs, use test.each instead of writing repetitive tests:
describe('isValidEmail', () => {
test.each([
['user@example.com', true],
['user@sub.example.com', true],
['invalid-email', false],
['@no-local.com', false],
['no-domain@', false],
['', false],
])('isValidEmail("%s") returns %s', (email, expected) => {
expect(isValidEmail(email)).toBe(expected);
});
});This runs six tests from a single test.each call. The output shows each case individually, so you know exactly which input failed.
You can also use an object syntax for clarity:
test.each([
{ input: 'hello', expected: 'HELLO' },
{ input: 'world', expected: 'WORLD' },
{ input: '', expected: '' },
])('toUpperCase("$input") returns "$expected"', ({ input, expected }) => {
expect(input.toUpperCase()).toBe(expected);
});Key Takeaways
- Use
describeto group tests andtestto define individual cases. toBefor primitives,toEqualfor objects and arrays.- Use
beforeEachfor test isolation andbeforeAllfor shared expensive setup. test.eacheliminates repetitive tests — define inputs and expected outputs in an array.- Run
jest --watchduring development for instant feedback on file changes.