
Jest Complete Guide: JavaScript Testing Framework for Modern Apps
Jest has become the standard testing framework for JavaScript and TypeScript projects, especially in the React ecosystem. Developed by Meta, it provides a complete testing solution out of the box - test runner, assertion library, mocking utilities, and code coverage - all with zero configuration for most projects.
This guide covers Jest from basic tests to advanced patterns that handle real-world testing challenges.
Table Of Contents-
Why Jest?
Jest stands out for several reasons:
- Zero configuration: Works out of the box for most projects
- Fast and parallel: Runs tests in parallel with smart test ordering
- Built-in mocking: Powerful mock functions and module mocking
- Snapshot testing: Capture and compare output over time
- Code coverage: Built-in coverage reporting
- Watch mode: Re-runs tests on file changes
- TypeScript support: First-class TypeScript integration
Jest is the default choice for React, Vue, and many Node.js projects.
Installation and Setup
Basic Installation
# npm
npm install --save-dev jest
# yarn
yarn add --dev jest
# With TypeScript
npm install --save-dev jest @types/jest ts-jestpackage.json Configuration
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}TypeScript Setup
# Initialize ts-jest
npx ts-jest config:initThis creates jest.config.js:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
}Writing Tests
Basic Test Structure
// sum.js
function sum(a, b) {
return a + b
}
module.exports = sum
// sum.test.js
const sum = require('./sum')
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})Test Organization
describe('Calculator', () => {
describe('addition', () => {
test('adds positive numbers', () => {
expect(sum(1, 2)).toBe(3)
})
test('adds negative numbers', () => {
expect(sum(-1, -2)).toBe(-3)
})
})
describe('subtraction', () => {
test('subtracts numbers', () => {
expect(subtract(5, 3)).toBe(2)
})
})
})Test Aliases
Jest provides multiple ways to write tests:
// These are equivalent
test('description', () => {})
it('description', () => {})
// Focus on specific tests
test.only('runs only this test', () => {})
it.only('runs only this test', () => {})
// Skip tests
test.skip('skipped test', () => {})
it.skip('skipped test', () => {})
// Parameterized tests
test.each([
[1, 2, 3],
[2, 3, 5],
[5, 5, 10],
])('adds %i + %i to equal %i', (a, b, expected) => {
expect(sum(a, b)).toBe(expected)
})Matchers
Matchers let you validate values in different ways.
Common Matchers
// Exact equality
expect(2 + 2).toBe(4)
// Object equality (deep)
expect({ name: 'John' }).toEqual({ name: 'John' })
// Truthiness
expect(null).toBeNull()
expect(undefined).toBeUndefined()
expect(value).toBeDefined()
expect(true).toBeTruthy()
expect(false).toBeFalsy()
// Numbers
expect(4).toBeGreaterThan(3)
expect(4).toBeGreaterThanOrEqual(4)
expect(4).toBeLessThan(5)
expect(0.1 + 0.2).toBeCloseTo(0.3)
// Strings
expect('hello world').toMatch(/world/)
expect('hello').toContain('ell')
// Arrays
expect([1, 2, 3]).toContain(2)
expect(['apple', 'banana']).toContainEqual('banana')
expect([1, 2, 3]).toHaveLength(3)
// Objects
expect({ name: 'John', age: 30 }).toHaveProperty('name')
expect({ name: 'John' }).toHaveProperty('name', 'John')
expect({ a: { b: 2 } }).toHaveProperty('a.b', 2)Negation
expect(1 + 1).not.toBe(3)
expect([1, 2]).not.toContain(3)
expect({ a: 1 }).not.toHaveProperty('b')Exception Matchers
function throwError() {
throw new Error('Something went wrong')
}
test('throws an error', () => {
expect(() => throwError()).toThrow()
expect(() => throwError()).toThrow(Error)
expect(() => throwError()).toThrow('Something went wrong')
expect(() => throwError()).toThrow(/wrong/)
})Use .toBe() for primitives and .toEqual() for objects and arrays.
.toBe() uses Object.is() for comparison, while .toEqual() performs deep
equality checking.
Setup and Teardown
Per-Test Setup
describe('Database tests', () => {
let db
beforeEach(() => {
db = new Database()
db.connect()
})
afterEach(() => {
db.disconnect()
})
test('inserts data', () => {
db.insert({ name: 'John' })
expect(db.count()).toBe(1)
})
test('queries data', () => {
db.insert({ name: 'Jane' })
const result = db.query({ name: 'Jane' })
expect(result).toHaveLength(1)
})
})One-Time Setup
describe('API tests', () => {
let server
beforeAll(async () => {
server = await startServer()
})
afterAll(async () => {
await server.close()
})
test('responds to GET /', async () => {
const response = await fetch('http://localhost:3000/')
expect(response.status).toBe(200)
})
})Scoped Setup
beforeAll(() => console.log('1 - beforeAll'))
afterAll(() => console.log('1 - afterAll'))
beforeEach(() => console.log('1 - beforeEach'))
afterEach(() => console.log('1 - afterEach'))
describe('Scoped tests', () => {
beforeAll(() => console.log('2 - beforeAll'))
afterAll(() => console.log('2 - afterAll'))
beforeEach(() => console.log('2 - beforeEach'))
afterEach(() => console.log('2 - afterEach'))
test('example', () => console.log('2 - test'))
})
// Output:
// 1 - beforeAll
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAllMocking
Mock Functions
test('mock function basics', () => {
const mockFn = jest.fn()
mockFn('hello')
mockFn('world')
expect(mockFn).toHaveBeenCalled()
expect(mockFn).toHaveBeenCalledTimes(2)
expect(mockFn).toHaveBeenCalledWith('hello')
expect(mockFn).toHaveBeenLastCalledWith('world')
})Mock Return Values
const mockFn = jest.fn()
// Single return value
mockFn.mockReturnValue(42)
expect(mockFn()).toBe(42)
// Return once
mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValue(3)
expect(mockFn()).toBe(1)
expect(mockFn()).toBe(2)
expect(mockFn()).toBe(3)
expect(mockFn()).toBe(3)
// Custom implementation
mockFn.mockImplementation((x) => x * 2)
expect(mockFn(5)).toBe(10)Mocking Modules
// api.js
export const fetchUser = (id) => fetch(`/users/${id}`)
// user.test.js
import { fetchUser } from './api'
import { getUser } from './user'
jest.mock('./api')
test('gets user', async () => {
fetchUser.mockResolvedValue({
json: () => Promise.resolve({ id: 1, name: 'John' }),
})
const user = await getUser(1)
expect(user.name).toBe('John')
expect(fetchUser).toHaveBeenCalledWith(1)
})Partial Mocking
// Mock specific functions, keep others
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
formatDate: jest.fn(() => '2024-01-01'),
}))Spying
const video = {
play() {
return true
},
pause() {
return true
},
}
test('plays video', () => {
const spy = jest.spyOn(video, 'play')
video.play()
expect(spy).toHaveBeenCalled()
spy.mockRestore() // Restore original implementation
})Async Testing
Promises
// Return the promise
test('resolves to data', () => {
return fetchData().then((data) => {
expect(data).toBe('data')
})
})
// Using resolves/rejects
test('resolves to data', () => {
return expect(fetchData()).resolves.toBe('data')
})
test('rejects with error', () => {
return expect(fetchBadData()).rejects.toThrow('error')
})Async/Await
test('async/await', async () => {
const data = await fetchData()
expect(data).toBe('data')
})
test('async error', async () => {
await expect(fetchBadData()).rejects.toThrow('error')
})Callbacks
test('callback style', (done) => {
fetchData((error, data) => {
try {
expect(error).toBeNull()
expect(data).toBe('data')
done()
} catch (e) {
done(e)
}
})
})Timers
jest.useFakeTimers()
test('timer-based code', () => {
const callback = jest.fn()
setTimeout(callback, 1000)
// Fast-forward time
jest.advanceTimersByTime(1000)
expect(callback).toHaveBeenCalled()
})
test('run all timers', () => {
const callback = jest.fn()
setTimeout(callback, 1000)
setTimeout(callback, 2000)
jest.runAllTimers()
expect(callback).toHaveBeenCalledTimes(2)
})Snapshot Testing
Snapshots capture output and compare against future runs.
Basic Snapshots
test('renders correctly', () => {
const component = render(<Button label="Click me" />)
expect(component).toMatchSnapshot()
})Inline Snapshots
test('user object', () => {
const user = getUser(1)
expect(user).toMatchInlineSnapshot(`
Object {
"id": 1,
"name": "John",
}
`)
})Updating Snapshots
# Update all snapshots
jest --updateSnapshot
jest -u
# Interactive update
jest --watch
# Press 'u' to update failing snapshots⚠️
Review snapshot changes carefully before committing. Large snapshots can make reviews difficult - consider using inline snapshots or extracting specific assertions for complex objects.
Configuration
jest.config.js
module.exports = {
// Test environment
testEnvironment: 'jsdom', // or 'node'
// Test file patterns
testMatch: ['**/__tests__/**/*.js', '**/*.test.js'],
// Files to ignore
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
// Module resolution
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less)$': 'identity-obj-proxy',
},
// Setup files
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// Coverage
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
},
},
// Transform
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
}Common CLI Options
# Watch mode
jest --watch
# Run specific tests
jest user.test.js
jest --testNamePattern="login"
# Coverage
jest --coverage
jest --coverageReporters="html"
# Verbose output
jest --verbose
# Run in band (sequential)
jest --runInBand
# Clear cache
jest --clearCacheBest Practices
Organize Tests Alongside Code
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx
│ │ └── index.ts
│ └── Form/
│ ├── Form.tsx
│ └── Form.test.tsx
└── utils/
├── format.ts
└── format.test.tsDescriptive Test Names
// Good - describes behavior
test('returns null when user is not found', () => {})
test('throws ValidationError for invalid email', () => {})
// Avoid - vague
test('works correctly', () => {})
test('test 1', () => {})AAA Pattern
test('calculates total with discount', () => {
// Arrange
const cart = new ShoppingCart()
cart.addItem({ price: 100 })
cart.applyDiscount(0.1)
// Act
const total = cart.getTotal()
// Assert
expect(total).toBe(90)
})Avoid Test Interdependence
// Bad - tests share state
let counter = 0
test('increments', () => {
counter++
expect(counter).toBe(1)
})
test('decrements', () => {
counter--
expect(counter).toBe(0)
})
// Good - each test is independent
test('increments', () => {
const counter = new Counter(0)
counter.increment()
expect(counter.value).toBe(1)
})Mock External Dependencies
// Mock fetch for all tests
global.fetch = jest.fn()
beforeEach(() => {
fetch.mockClear()
})
test('fetches user', async () => {
fetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({ id: 1 }),
})
const user = await getUser(1)
expect(user.id).toBe(1)
})Jest's comprehensive feature set makes it ideal for testing modern JavaScript applications. From simple unit tests to complex integration scenarios with mocking and async handling, Jest provides the tools you need while maintaining fast, reliable test execution.
Quiz on Jest
Your Score: 0/10
Question: What is the difference between toBe() and toEqual() in Jest?
Continue Reading
The Software Testing Lifecycle: An OverviewDive into the crucial phase of Test Requirement Analysis in the Software Testing Lifecycle, understanding its purpose, activities, deliverables, and best practices to ensure a successful software testing process.Types of Software TestingThis article provides a comprehensive overview of the different types of software testing.Accessibility TestingLearn about accessibility testing, its importance, types, best practices, and tools.Unit Testing in SoftwareLearn the fundamentals of unit testing in software, its importance in functional testing, and how to ensure early bug detection, improved code quality, and seamless collaboration among team members.Integration TestingLearn the essentials of integration testing, its importance, types, best practices, and tools.System TestingLearn about system testing, its importance, types, techniques, process, best practices, and tools to effectively validate software systems.Performance TestingLearn about performance testing, its importance, types, techniques, process, best practices, and tools to effectively validate software systems.Security TestingLearn about security testing, its importance, types, techniques, process, best practices, and tools to effectively validate software systems.User Acceptance TestingLearn about user acceptance testing, its importance, types, techniques, process, best practices, and tools to effectively validate software systems.
Frequently Asked Questions (FAQs) / People Also Ask (PAA)
How does Jest compare to Mocha or Jasmine?
Can I use Jest with TypeScript?
How do I test React components with Jest?
Why are my tests running slowly?
How do I debug failing tests?
How do I mock environment variables?
What's the difference between mockReturnValue and mockImplementation?
How do I generate and view code coverage?