Understanding Jest test generation
The describe/it/expect rhythm, and the AAA pattern under it.
What a good unit test looks like, how Jest's matcher vocabulary compares to Vitest, and the mocks that solve more problems than they cause.
describe, it, expect.
Jest's vocabulary: describe() groups related tests; it() ortest() defines one test case; expect() declares an assertion. The structure reads aloud: "describe a user — it should hash the password — expect the hash to be 60 characters". Test files conventionally end in.test.ts; Jest picks them up automatically. The convention is wholesale inherited by Vitest, so test files port across with a config swap.
The AAA pattern.
Arrange-Act-Assert. Arrange: set up the inputs and dependencies. Act: call the function under test. Assert: check the output. Keep these three sections visually separated in every test — blank line, or comment dividers. The result is tests that read in one direction; debugging a failure means starting at Assert and walking backward. Tests that interleave arrange and act ("call this, set this up, call that, assert this, call again") are the ones that take twenty minutes to understand.
Matchers.
toBe() checks reference equality (===). toEqual() deep equals. toStrictEqual() like toEqual but rejects extra properties.toMatchObject() like toEqual but allows extras (partial match).toThrow() asserts the function throws.toHaveBeenCalledWith() asserts a mock was called with the args.toMatchSnapshot() writes the value to disk, compares on subsequent runs. Pick the narrowest matcher that captures your intent — false positives cost less than false negatives.
A worked test.
A function add(a, b) returns their sum. The Jest test:describe("add", () => { it("adds positives", () => {expect(add(2, 3)).toBe(5); }); it("handles negatives", () => {expect(add(-1, -2)).toBe(-3); }); });. Two test cases, both AAA-shaped, both using the narrow toBe. Run with jest add.test.ts; passes in <100 ms. The pattern repeats for every unit; nothing else to learn.
add() tested
describe → it → expect
One AAA block per case.
expect(add(2,3)).toBe(5) ; expect(add(-1,-2)).toBe(-3)
= 2 passing
Mocks: powerful, dangerous.
jest.fn() creates a function whose call history Jest tracks.jest.mock(modulePath) replaces an entire module's exports with auto-mocks.jest.spyOn() wraps an existing method to record calls without replacing behaviour. Mocks let you isolate the unit under test from its dependencies — but over-mocking ends in tests that pass while the code is broken. The rule of thumb: mock the I/O boundary (network, filesystem, time), not your own modules.
Vitest is the modern alternative.
Vitest reuses Jest's API exactly, runs on Vite's transform pipeline, and is roughly 10× faster for TypeScript test suites. The describe/it/expect code ports unchanged; only the config file changes. New projects almost always pick Vitest now. Existing Jest projects migrate as the slowness starts to hurt — usually around 2,000 tests. The Jest-generated test fixtures from this tool work in both runners.