Jest Testing Framework — Complete Guide with Examples
Jest is a JavaScript testing framework developed by Meta that provides a complete test runner, assertion library, mocking utilities, and code coverage — all out of the box with zero configuration for most projects.
What You’ll Learn
By the end of this tutorial, you’ll be able to set up Jest in any JavaScript or TypeScript project, write effective unit tests using matchers and mocks, test asynchronous code, generate coverage reports, and integrate Jest into your CI pipeline.
Why Jest Matters
Jest is the most popular testing framework in the JavaScript ecosystem. It’s the default choice for React applications, used by millions of developers, and maintains a rich plugin ecosystem. At DodaTech, Jest is the backbone of unit testing for Doda Browser’s rendering engine and DodaZIP’s compression algorithms.
Jest Learning Path
flowchart LR
A[Testing Basics] --> B[Jest]
B --> C[Testing Library]
B --> D[Playwright]
B --> E[Cypress]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Setting Up Jest
Create a new project and install Jest:
mkdir jest-demo && cd jest-demo
npm init -y
npm install --save-dev jestAdd the test script to package.json:
{
"scripts": {
"test": "jest"
}
}For TypeScript projects, add ts-jest:
npm install --save-dev jest ts-jest @types/jest typescript
npx ts-jest config:initCreate your first test file:
// sum.test.js
const sum = (a, b) => a + b;
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});Expected output:
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (2 ms)Matchers
Matchers are methods on the expect object that assert values in different ways.
Common Matchers
test('common matchers', () => {
// Equality
expect(2 + 2).toBe(4);
expect({ name: 'Alice' }).toEqual({ name: 'Alice' });
// Truthiness
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect(1).toBeDefined();
expect(true).toBeTruthy();
expect(0).toBeFalsy();
// Numbers
expect(10).toBeGreaterThan(5);
expect(10).toBeLessThanOrEqual(10);
expect(0.1 + 0.2).toBeCloseTo(0.3);
// Strings
expect('hello world').toMatch(/world/);
expect('hello world').toContain('world');
// Arrays
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toHaveLength(3);
// Objects
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 });
expect({ a: 1 }).toHaveProperty('a');
});Expected output:
PASS ./matchers.test.js
✓ common matchers (3 ms)Testing Asynchronous Code
Jest handles promises, async/await, and callbacks natively.
// asyncOperations.js
function fetchUser(id) {
return Promise.resolve({ id, name: 'Alice' });
}
function fetchUsers() {
return Promise.reject(new Error('Network error'));
}
// asyncOperations.test.js
const { fetchUser, fetchUsers } = require('./asyncOperations');
test('fetchUser returns user by id', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('Alice');
});
test('fetchUser resolves with correct id', () => {
return expect(fetchUser(2)).resolves.toHaveProperty('id', 2);
});
test('fetchUsers rejects with error', async () => {
await expect(fetchUsers()).rejects.toThrow('Network error');
});Expected output:
PASS ./asyncOperations.test.js
✓ fetchUser returns user by id (3 ms)
✓ fetchUser resolves with correct id (1 ms)
✓ fetchUsers rejects with error (2 ms)Mocks and Spies
Jest mocks let you replace real implementations with controlled test doubles.
// paymentService.js
const api = require('./api');
async function processPayment(userId, amount) {
const user = await api.getUser(userId);
if (user.balance < amount) throw new Error('Insufficient funds');
return api.charge(userId, amount);
}
// paymentService.test.js
const api = require('./api');
const { processPayment } = require('./paymentService');
jest.mock('./api');
test('processPayment charges user when balance is sufficient', async () => {
api.getUser.mockResolvedValue({ id: 1, balance: 100 });
api.charge.mockResolvedValue({ success: true, transactionId: 'txn_123' });
const result = await processPayment(1, 50);
expect(result.success).toBe(true);
expect(api.charge).toHaveBeenCalledWith(1, 50);
expect(api.charge).toHaveBeenCalledTimes(1);
});
test('processPayment throws when balance is insufficient', async () => {
api.getUser.mockResolvedValue({ id: 1, balance: 10 });
await expect(processPayment(1, 50)).rejects.toThrow('Insufficient funds');
expect(api.charge).not.toHaveBeenCalled();
});Expected output:
PASS ./paymentService.test.js
✓ processPayment charges user when balance is sufficient (4 ms)
✓ processPayment throws when balance is insufficient (2 ms)Code Coverage
Jest includes built-in code coverage via the --coverage flag:
npx jest --coverageThis generates a terminal report and an HTML report in the coverage/ directory showing:
- % Stmts: percentage of statements covered
- % Branch: percentage of branches (if/else, switch) covered
- % Funcs: percentage of functions called
- % Lines: percentage of executable lines covered
The Jest Testing Flow
flowchart LR
A[Write Test] --> B[Run jest]
B --> C{Pass?}
C -->|Yes| D[Refactor]
C -->|No| E[Fix Code]
E --> B
D --> A
Common Jest Mistakes
1. Forgetting to Return or Await a Promise
If a test function returns a promise (like an async function), Jest waits for it to resolve. If you forget the await, the test passes before the assertion runs.
Fix: Always await async assertions or return the promise explicitly.
2. Using toBe for Objects and Arrays
toBe uses Object.is — it’s reference equality, not deep equality. Two objects with the same properties are different references.
Fix: Use toEqual or toStrictEqual for comparing objects and arrays.
3. Mocks Persisting Between Tests
If a mock’s implementation or return value bleeds from one test into the next, tests become interdependent.
Fix: Use beforeEach to clear mocks: jest.clearAllMocks() or jest.resetAllMocks().
4. Testing Too Much in One Test
A single test that checks five different behaviors makes failures hard to diagnose.
Fix: One assertion or behavior per test. If you have multiple expectations about related state, use describe to group them.
5. Not Using describe Blocks
Flat test files with 20+ tests become hard to navigate and read.
Fix: Group related tests with describe blocks.
6. Mocking Modules Incorrectly
Jest’s hoisting of jest.mock calls can cause confusing errors. The call is hoisted to the top of the file, but the variable assignment isn’t.
Fix: Place jest.mock calls at the top of the file, outside any describe or test blocks.
7. Ignoring the Coverage Report
Coverage below 80% on critical modules indicates untested paths.
Fix: Run --coverage regularly and review uncovered lines.
Practice Questions
1. How do you test that a function throws an error in Jest?
Wrap the function call in a function: expect(() => fn()).toThrow('error message'). For async functions: await expect(fn()).rejects.toThrow('error').
2. What’s the difference between toBe and toEqual?
toBe checks reference equality (same object in memory). toEqual checks deep equality (same properties and values).
3. How do you mock a module in Jest?
Call jest.mock('./module') at the top of the test file. Then access the mock via require('./module') and use .mockResolvedValue(), .mockImplementation(), etc.
4. How do you run only one test file?
npx jest path/to/test-file.test.js or use .only on a test: test.only('name', fn).
5. Challenge: Write tests for an array utility library.
Implement and test: chunk(array, size) splits an array into groups of size, unique(array) removes duplicates, and flatten(array) flattens one level.
Mini Project: Jest Test Suite for a Task Queue
// taskQueue.js
class TaskQueue {
constructor() {
this.tasks = [];
this.running = false;
}
add(task) {
this.tasks.push(task);
return this.tasks.length;
}
async runAll() {
this.running = true;
const results = [];
while (this.tasks.length > 0) {
const task = this.tasks.shift();
results.push(await task());
}
this.running = false;
return results;
}
get pending() {
return this.tasks.length;
}
get isRunning() {
return this.running;
}
}
module.exports = TaskQueue;
// taskQueue.test.js
const TaskQueue = require('./taskQueue');
describe('TaskQueue', () => {
let queue;
beforeEach(() => {
queue = new TaskQueue();
});
test('starts empty', () => {
expect(queue.pending).toBe(0);
expect(queue.isRunning).toBe(false);
});
test('adds tasks and returns count', () => {
const count = queue.add(() => Promise.resolve(1));
expect(count).toBe(1);
expect(queue.pending).toBe(1);
});
test('runs all tasks and returns results in order', async () => {
queue.add(() => Promise.resolve('a'));
queue.add(() => Promise.resolve('b'));
const results = await queue.runAll();
expect(results).toEqual(['a', 'b']);
expect(queue.pending).toBe(0);
});
test('sets running state during execution', async () => {
queue.add(() => Promise.resolve(1));
const runPromise = queue.runAll();
expect(queue.isRunning).toBe(true);
await runPromise;
expect(queue.isRunning).toBe(false);
});
});Expected output:
PASS ./taskQueue.test.js
TaskQueue
✓ starts empty (2 ms)
✓ adds tasks and returns count (1 ms)
✓ runs all tasks and returns results in order (3 ms)
✓ sets running state during execution (2 ms)FAQ
Try It Yourself
- Create a new Node.js project and install Jest
- Write a test for a simple utility function (like
capitalize(str)) - Run
npx jest --watchto watch files for changes - Add a mock and verify it was called correctly
- Run with
--coverageand view the HTML report
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro