TL;DR
- Jest is a zero-config testing framework that includes assertions, mocking, and coverage
- Matchers like
toBe,toEqual,toContainmake assertions readable- Mock functions with
jest.fn(), modules withjest.mock(), timers withjest.useFakeTimers()- Async testing: use
async/await,resolves/rejects, or callbackdone- Snapshot testing captures UI output — useful for React components
Best for: JavaScript/TypeScript developers, React/Vue/Node.js projects, teams wanting all-in-one testing Skip if: You need browser-based testing (use Playwright/Cypress instead) Read time: 15 minutes
Your test suite runs for 10 minutes. Half the tests are flaky. Nobody trusts the results anymore.
Jest changes this. It’s fast, reliable, and works out of the box. No configuration hell. No piecing together five different libraries.
This tutorial teaches Jest from scratch — matchers, mocking, async testing, and the patterns that make tests maintainable.
What is Jest?
Jest is a JavaScript testing framework created by Facebook. It runs your tests, provides assertions, mocks dependencies, and generates coverage reports — all in one package.
What Jest includes:
- Test runner — finds and executes test files
- Assertion library —
expect()with built-in matchers - Mocking — mock functions, modules, timers
- Coverage — built-in code coverage reports
- Snapshot testing — capture and compare output
- Watch mode — re-run tests on file changes
Installation and Setup
New Project
# Initialize project
npm init -y
# Install Jest
npm install --save-dev jest
# Add test script to package.json
npm pkg set scripts.test="jest"
TypeScript Project
npm install --save-dev jest typescript ts-jest @types/jest
# Initialize ts-jest config
npx ts-jest config:init
Create React App
Jest is already included. Just run:
npm test
Project Structure
my-project/
├── src/
│ ├── calculator.js
│ └── utils/
│ └── formatters.js
├── __tests__/
│ ├── calculator.test.js
│ └── utils/
│ └── formatters.test.js
├── jest.config.js
└── package.json
Jest finds tests in:
- Files ending with
.test.jsor.spec.js - Files in
__tests__folders
Writing Your First Test
// src/calculator.js
function add(a, b) {
return a + b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
module.exports = { add, divide };
// __tests__/calculator.test.js
const { add, divide } = require('../src/calculator');
describe('Calculator', () => {
describe('add', () => {
test('adds two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('adds negative numbers', () => {
expect(add(-1, -1)).toBe(-2);
});
test('adds zero', () => {
expect(add(5, 0)).toBe(5);
});
});
describe('divide', () => {
test('divides two numbers', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
});
});
});
Running Tests
# Run all tests
npm test
# Run specific file
npm test -- calculator.test.js
# Run tests matching pattern
npm test -- --testNamePattern="adds"
# Watch mode
npm test -- --watch
# With coverage
npm test -- --coverage
Matchers
Matchers are methods that check values. Jest has 50+ built-in matchers.
Common Matchers
// Equality
expect(2 + 2).toBe(4); // Strict equality (===)
expect({ a: 1 }).toEqual({ a: 1 }); // Deep equality
// Truthiness
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('value').toBeDefined();
// Numbers
expect(10).toBeGreaterThan(5);
expect(10).toBeGreaterThanOrEqual(10);
expect(5).toBeLessThan(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // Floating point
// Strings
expect('Hello World').toMatch(/World/);
expect('Hello World').toContain('World');
// Arrays
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toHaveLength(3);
expect(['a', 'b']).toEqual(expect.arrayContaining(['a']));
// Objects
expect({ a: 1, b: 2 }).toHaveProperty('a');
expect({ a: 1, b: 2 }).toHaveProperty('a', 1);
expect({ a: 1 }).toMatchObject({ a: 1 });
// Exceptions
expect(() => { throw new Error('fail'); }).toThrow();
expect(() => { throw new Error('fail'); }).toThrow('fail');
expect(() => { throw new Error('fail'); }).toThrow(Error);
Negating Matchers
Add .not before any matcher:
expect(5).not.toBe(3);
expect([1, 2]).not.toContain(3);
expect({ a: 1 }).not.toHaveProperty('b');
Testing Async Code
Async/Await
// src/api.js
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
}
return response.json();
}
module.exports = { fetchUser };
// __tests__/api.test.js
test('fetches user successfully', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('John');
});
test('throws error for invalid user', async () => {
await expect(fetchUser(999)).rejects.toThrow('User not found');
});
Promises with resolves/rejects
test('resolves to user data', () => {
return expect(fetchUser(1)).resolves.toMatchObject({ name: 'John' });
});
test('rejects for missing user', () => {
return expect(fetchUser(999)).rejects.toThrow();
});
Callback Style (done)
function fetchDataWithCallback(callback) {
setTimeout(() => {
callback({ data: 'result' });
}, 100);
}
test('calls callback with data', (done) => {
function callback(result) {
expect(result.data).toBe('result');
done(); // Test waits until done() is called
}
fetchDataWithCallback(callback);
});
Mocking
Mocking replaces real implementations with controlled substitutes.
Mock Functions (jest.fn)
test('mock function tracks calls', () => {
const mockCallback = jest.fn(x => x + 1);
[1, 2, 3].forEach(mockCallback);
// Check call count
expect(mockCallback).toHaveBeenCalledTimes(3);
// Check specific calls
expect(mockCallback).toHaveBeenCalledWith(1);
expect(mockCallback).toHaveBeenLastCalledWith(3);
// Check return values
expect(mockCallback.mock.results[0].value).toBe(2);
});
Mock Return Values
const mock = jest.fn();
// Return different values on successive calls
mock
.mockReturnValueOnce(10)
.mockReturnValueOnce(20)
.mockReturnValue(30);
console.log(mock()); // 10
console.log(mock()); // 20
console.log(mock()); // 30
console.log(mock()); // 30
// Mock resolved/rejected promises
const asyncMock = jest.fn()
.mockResolvedValueOnce({ success: true })
.mockRejectedValueOnce(new Error('Failed'));
Mocking Modules
// Mock entire module
jest.mock('./api');
const { fetchUser } = require('./api');
// Set up mock implementation
fetchUser.mockResolvedValue({ id: 1, name: 'Mock User' });
test('uses mocked API', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('Mock User');
});
Mocking with Factory
jest.mock('./database', () => ({
connect: jest.fn().mockResolvedValue(true),
query: jest.fn().mockResolvedValue([{ id: 1 }]),
close: jest.fn()
}));
Spying on Methods
const video = {
play() {
return true;
}
};
test('spy on method', () => {
const spy = jest.spyOn(video, 'play');
video.play();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveReturnedWith(true);
spy.mockRestore(); // Restore original implementation
});
Mocking Timers
jest.useFakeTimers();
function delayedGreeting(callback) {
setTimeout(() => callback('Hello'), 1000);
}
test('calls callback after delay', () => {
const callback = jest.fn();
delayedGreeting(callback);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000); // Fast-forward time
expect(callback).toHaveBeenCalledWith('Hello');
});
// Or run all timers
test('with runAllTimers', () => {
const callback = jest.fn();
delayedGreeting(callback);
jest.runAllTimers();
expect(callback).toHaveBeenCalled();
});
Setup and Teardown
describe('Database tests', () => {
let db;
// Runs once before all tests in this describe
beforeAll(async () => {
db = await connectToDatabase();
});
// Runs once after all tests
afterAll(async () => {
await db.close();
});
// Runs before each test
beforeEach(async () => {
await db.clear();
});
// Runs after each test
afterEach(() => {
jest.clearAllMocks();
});
test('inserts record', async () => {
await db.insert({ name: 'Test' });
const records = await db.findAll();
expect(records).toHaveLength(1);
});
});
Snapshot Testing
Snapshots capture output and detect unintended changes.
// src/formatUser.js
function formatUser(user) {
return {
displayName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
initials: `${user.firstName[0]}${user.lastName[0]}`
};
}
module.exports = { formatUser };
// __tests__/formatUser.test.js
const { formatUser } = require('../src/formatUser');
test('formats user correctly', () => {
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'John.Doe@Example.com'
};
expect(formatUser(user)).toMatchSnapshot();
});
First run creates __snapshots__/formatUser.test.js.snap:
exports[`formats user correctly 1`] = `
{
"displayName": "John Doe",
"email": "john.doe@example.com",
"initials": "JD"
}
`;
If output changes, test fails. Update snapshots:
npm test -- --updateSnapshot
# or
npm test -- -u
Inline Snapshots
test('formats user inline', () => {
const user = { firstName: 'John', lastName: 'Doe', email: 'J@E.com' };
expect(formatUser(user)).toMatchInlineSnapshot(`
{
"displayName": "John Doe",
"email": "j@e.com",
"initials": "JD"
}
`);
});
Code Coverage
# Generate coverage report
npm test -- --coverage
# With specific thresholds
npm test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80}}'
Coverage Configuration
// jest.config.js
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
coverageReporters: ['text', 'lcov', 'html']
};
Jest Configuration
// jest.config.js
module.exports = {
// Test environment
testEnvironment: 'node', // or 'jsdom' for browser
// File patterns
testMatch: ['**/__tests__/**/*.test.js'],
testPathIgnorePatterns: ['/node_modules/'],
// Transform files
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
// Module resolution
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less)$': 'identity-obj-proxy'
},
// Setup files
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// Timeouts
testTimeout: 10000,
// Parallelization
maxWorkers: '50%'
};
Best Practices
1. One Assertion Per Test (Usually)
// Bad: multiple unrelated assertions
test('user validation', () => {
expect(isValidEmail('test@example.com')).toBe(true);
expect(isValidEmail('invalid')).toBe(false);
expect(isValidName('John')).toBe(true);
});
// Good: separate tests
test('accepts valid email', () => {
expect(isValidEmail('test@example.com')).toBe(true);
});
test('rejects invalid email', () => {
expect(isValidEmail('invalid')).toBe(false);
});
2. Descriptive Test Names
// Bad
test('test1', () => { ... });
// Good
test('returns null when user ID is not found', () => { ... });
3. Arrange-Act-Assert Pattern
test('calculates total with discount', () => {
// Arrange
const cart = { items: [{ price: 100 }, { price: 50 }] };
const discount = 0.1;
// Act
const total = calculateTotal(cart, discount);
// Assert
expect(total).toBe(135);
});
4. Avoid Testing Implementation Details
// Bad: tests internal state
test('sets internal flag', () => {
const counter = new Counter();
counter.increment();
expect(counter._count).toBe(1); // Testing private property
});
// Good: tests public behavior
test('increments count', () => {
const counter = new Counter();
counter.increment();
expect(counter.getCount()).toBe(1); // Testing public method
});
5. Use beforeEach for Common Setup
describe('ShoppingCart', () => {
let cart;
beforeEach(() => {
cart = new ShoppingCart();
cart.addItem({ id: 1, price: 10 });
});
test('calculates subtotal', () => {
expect(cart.getSubtotal()).toBe(10);
});
test('applies discount', () => {
cart.applyDiscount(0.1);
expect(cart.getTotal()).toBe(9);
});
});
AI-Assisted Jest Testing
AI tools can accelerate test writing when used appropriately.
What AI does well:
- Generate test cases from function signatures
- Create mock data matching specific shapes
- Write boilerplate for common patterns
- Suggest edge cases you might miss
What still needs humans:
- Deciding what’s worth testing
- Verifying tests actually test the right thing
- Writing tests for complex business logic
- Debugging failing tests
Useful prompt:
I have this function:
function validateOrder(order) {
if (!order.items?.length) return { valid: false, error: 'No items' };
if (order.items.some(i => i.quantity <= 0)) return { valid: false, error: 'Invalid quantity' };
if (order.total !== order.items.reduce((sum, i) => sum + i.price * i.quantity, 0)) {
return { valid: false, error: 'Total mismatch' };
}
return { valid: true };
}
Generate Jest tests covering:
- Valid order
- Empty items array
- Missing items property
- Zero/negative quantity
- Mismatched total
FAQ
What is Jest used for?
Jest is a JavaScript testing framework used for unit tests, integration tests, and snapshot testing. It provides a test runner, assertion library, mocking utilities, and code coverage — all in one package. Jest works with React, Vue, Angular, Node.js, and any JavaScript or TypeScript project.
Is Jest only for React?
No. While Jest was created by Facebook and is popular in React projects, it works with any JavaScript or TypeScript codebase. Jest tests Node.js backend code, Vue components, Angular applications, and vanilla JavaScript equally well. React Testing Library is a separate package that complements Jest for React-specific testing.
What’s the difference between Jest and Mocha?
Jest is an all-in-one testing framework with built-in assertions (expect), mocking (jest.fn), and coverage. Mocha is a test runner that requires separate libraries: Chai for assertions, Sinon for mocking, nyc for coverage. Jest is easier to set up and configure; Mocha offers more flexibility and customization. For new projects, Jest is usually the simpler choice.
How do I mock API calls in Jest?
Several approaches work:
- jest.mock() — mock the entire fetch/axios module
- jest.spyOn() — spy on and mock specific methods
- Manual mocks — create
__mocks__folder with mock implementations - MSW (Mock Service Worker) — intercept network requests for realistic API mocking
For simple cases, jest.mock() is sufficient. For complex applications, MSW provides more realistic behavior.
Official Resources
See Also
- Jest & Testing Library for React - React component testing with Testing Library
- Mocha and Chai - Alternative JavaScript testing stack
- Test Automation Pyramid - Where unit tests fit in your strategy
- Cypress Tutorial - E2E testing for JavaScript applications
