Anatomy of a Jest Test File
A minimal Jest test file pairs the test() function (aliased as it()) with the expect() assertion API. test('description', () => { ... }) takes a human-readable string describing the behavior being verified and a callback containing the actual test logic; inside that callback, you call the function or component under test and assert on its result with expect(actual).matcher(expected). Jest automatically discovers and executes any file ending in .test.js/.test.ts or living inside a __tests__ folder without any import statements needed for test, expect, or describe — they're injected globally by the Jest runtime.
Cricket analogy: The test('description', callback) pattern is like a scorecard entry labeled 'LBW review — front foot no-ball check' followed by the actual replay footage — the string names what's being verified, and the callback is the replay logic that determines pass or fail.
The expect() API and Common Matchers
Jest ships dozens of matchers for different assertion needs: toBe for primitive equality (using Object.is), toEqual for deep equality on objects and arrays (ignoring undefined properties), toBeTruthy/toBeFalsy for boolean coercion checks, toContain for arrays and strings, toThrow for functions expected to throw errors, and toHaveBeenCalledWith for verifying mock function call arguments. Choosing the right matcher matters: using toBe on two structurally identical but distinct objects will fail because toBe checks reference equality, while toEqual correctly reports them as equal by comparing their contents.
Cricket analogy: Choosing toBe versus toEqual is like the difference between checking if two players are literally the same person (toBe, reference equality) versus checking if two different players have identical batting statistics (toEqual, comparing the actual numbers).
// mathUtils.js
function sum(a, b) {
return a + b;
}
function divide(a, b) {
if (b === 0) throw new Error('Cannot divide by zero');
return a / b;
}
module.exports = { sum, divide };
// mathUtils.test.js
const { sum, divide } = require('./mathUtils');
test('sum adds two positive numbers', () => {
expect(sum(2, 3)).toBe(5);
});
test('divide throws on division by zero', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
});
test('sum result is an equal but not identical object when wrapped', () => {
const result = { total: sum(2, 3) };
expect(result).toEqual({ total: 5 });
});Use test.todo('description') to stub out a test you plan to write later — it shows up in the output as pending, keeping a visible checklist without a failing placeholder.
Testing Asynchronous Code
For code that returns a Promise, mark your test callback async and await the call before asserting, e.g. test('fetches user', async () => { const user = await fetchUser(1); expect(user.name).toBe('Ada'); }). Alternatively, you can return the promise directly with .then(), or use the resolves/rejects matcher modifiers like await expect(fetchUser(1)).resolves.toHaveProperty('name', 'Ada'). A common mistake is forgetting to await or return the promise — the test function completes synchronously before the assertion runs, and the test passes even if the assertion inside would have failed.
Cricket analogy: Forgetting to await an async test is like a scorer walking away and filing the match result before the final over is bowled — the report gets submitted as 'complete' even though the actual outcome (a potential last-ball six) hasn't happened yet.
A test with a forgotten await doesn't error — it silently passes even when the underlying assertion would fail, because the test function returns before the promise resolves. The safest fix is always awaiting or returning promise-based assertions; consider the no-floating-promises ESLint rule to catch this before it merges.
Arrange, Act, Assert
A well-structured test follows the Arrange-Act-Assert pattern: Arrange sets up any inputs or mock data needed, Act invokes the function or component under test exactly once, and Assert checks the outcome with one or a small number of closely related expect() calls. Keeping each test focused on a single behavior — rather than asserting many unrelated things in one test() block — makes failures easier to diagnose, because a failing test name immediately tells you what broke without needing to read every assertion inside it.
Cricket analogy: Arrange-Act-Assert mirrors a fast bowler's routine: Arrange is the run-up and grip setup, Act is delivering the ball, and Assert is the umpire's single decision on that one delivery — not a bundled verdict covering the whole over at once.
- test('description', callback) (or its alias it) pairs a human-readable description with the actual test logic.
- test, expect, and describe are globally available in Jest test files without any imports.
- toBe checks reference/primitive equality; toEqual checks deep structural equality — using the wrong one causes false failures or false passes.
- toThrow, toContain, and toHaveBeenCalledWith cover error-throwing, membership, and mock-argument checks respectively.
- Async tests must await (or return) the promise, or the assertion may never actually run before the test completes.
- resolves/rejects matcher modifiers let you assert directly on a promise's eventual value or rejection.
- Follow Arrange-Act-Assert and keep each test focused on one behavior for clearer failure diagnosis.
Practice what you learned
1. What is the correct pairing for test()?
2. Which matcher should you use to compare two structurally identical but distinct objects?
3. What happens if you forget to await a promise inside an async test?
4. What does the 'Assert' step in Arrange-Act-Assert refer to?
5. What does test.todo('description') do?
Was this page helpful?
You May Also Like
What Is Jest?
An introduction to Jest, the all-in-one JavaScript testing framework built by Meta for unit, integration, and snapshot testing.
test() and describe() Blocks
How to organize related tests using describe() blocks, and the setup/teardown hooks that run alongside them.
Running and Watching Tests
How to run Jest from the command line, use watch mode for fast feedback, and interpret coverage output.
Installing and Configuring Jest
How to add Jest to a JavaScript or TypeScript project, and the key configuration options in jest.config.js.