Jest
GitHub Overview
jestjs/jest
Delightful JavaScript Testing.
Repository:https://github.com/jestjs/jest
Homepage:https://jestjs.io
Stars44,905
Watchers561
Forks6,574
Created:December 10, 2013
Language:TypeScript
License:MIT License
Topics
easyexpectationfacebookimmersivejavascriptpainlesspainless-javascript-testingsnapshottesting
Star History
Data as of: 7/20/2025, 02:27 AM
Testing Framework
Jest
Overview
Jest is a simple and comprehensive testing framework for JavaScript and TypeScript. Developed by Facebook (now Meta), it is widely adopted for testing React, Vue.js, and Node.js applications. Jest stands out for its zero-configuration approach and rich built-in features that enable immediate test creation without additional setup.
Details
Key Features
- Zero Configuration: Start testing immediately without additional setup
- Snapshot Testing: Automatically detect structural changes in UI components
- Powerful Mocking: Detailed control over functions, modules, and timers
- Parallel Execution: Run test files in parallel for faster execution
- Coverage Reports: Detailed code coverage measurement and visualization
- Watch Mode: Automatic test execution on file changes
- Rich Matchers: Built-in matchers for diverse validation patterns
Technical Background
Jest is built on top of Jasmine, significantly simplifying test environment setup. Integration with Babel provides direct support for modern JavaScript syntax and TypeScript, making it increasingly adopted in enterprise environments.
Enterprise Adoption Examples
- Meta: Standard testing tool for React and React Native projects
- Airbnb: Utilizes Jest for both frontend and backend testing
- Twitter: JavaScript microservices test automation
- Spotify: Quality assurance processes for Node.js APIs
Pros and Cons
Pros
- Fast Start: Begin creating tests immediately without configuration
- Comprehensive Features: Mock, snapshot, and coverage functionality built-in
- Excellent Developer Experience: Detailed error messages and watch mode
- Rich Ecosystem: Integration with numerous plugins and tools
- Strong Async Support: Intuitive testing of Promises and async/await
- Enterprise-Level Track Record: Proven reliability in large-scale projects
Cons
- Memory Usage: Can become heavy in large-scale projects
- Snapshot Management: Risk of tests becoming meaningless with inappropriate updates
- Configuration Complexity: Learning curve for advanced customization
- Parallel Execution Limitations: Race condition issues in state-sharing tests
Reference Pages
Code Examples
Hello World (Basic Test)
// math.js
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { add, multiply };
// math.test.js
const { add, multiply } = require('./math');
describe('Math functions', () => {
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test('multiplies 3 * 4 to equal 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('adds negative numbers correctly', () => {
expect(add(-1, -2)).toBe(-3);
});
});
Matcher Usage Examples
// user.test.js
describe('Jest matchers example', () => {
const user = {
name: 'John Doe',
age: 30,
email: '[email protected]',
hobbies: ['reading', 'gaming'],
address: {
city: 'Tokyo',
country: 'Japan'
}
};
test('equality matchers', () => {
expect(user.name).toBe('John Doe'); // Strict equality
expect(user.age).toEqual(30); // Deep equality
expect(user.hobbies).toEqual(['reading', 'gaming']);
});
test('truthiness matchers', () => {
expect(user.name).toBeTruthy();
expect(user.spouse).toBeFalsy();
expect(user.address).toBeDefined();
expect(user.phone).toBeUndefined();
expect(user.email).not.toBeNull();
});
test('number matchers', () => {
expect(user.age).toBeGreaterThan(25);
expect(user.age).toBeGreaterThanOrEqual(30);
expect(user.age).toBeLessThan(40);
expect(user.age).toBeCloseTo(30.1, 0);
});
test('string matchers', () => {
expect(user.email).toMatch(/.*@.*\.com/);
expect(user.name).toContain('John');
});
test('array matchers', () => {
expect(user.hobbies).toContain('reading');
expect(user.hobbies).toHaveLength(2);
expect(['apple', 'banana', 'orange']).toContainEqual('banana');
});
test('object matchers', () => {
expect(user).toHaveProperty('name');
expect(user).toHaveProperty('address.city', 'Tokyo');
expect(user.address).toMatchObject({ city: 'Tokyo' });
});
});
Mock Functionality
// api.js
const axios = require('axios');
class UserService {
async getUser(id) {
const response = await axios.get(`/api/users/${id}`);
return response.data;
}
async createUser(userData) {
const response = await axios.post('/api/users', userData);
return response.data;
}
}
module.exports = UserService;
// api.test.js
const axios = require('axios');
const UserService = require('./api');
// Mock entire axios module
jest.mock('axios');
const mockedAxios = axios;
describe('UserService', () => {
let userService;
beforeEach(() => {
userService = new UserService();
// Clear mocks before each test
jest.clearAllMocks();
});
test('should fetch user successfully', async () => {
const userData = { id: 1, name: 'John Doe', email: '[email protected]' };
// Set mock return value
mockedAxios.get.mockResolvedValue({ data: userData });
const result = await userService.getUser(1);
expect(mockedAxios.get).toHaveBeenCalledWith('/api/users/1');
expect(mockedAxios.get).toHaveBeenCalledTimes(1);
expect(result).toEqual(userData);
});
test('should create user successfully', async () => {
const newUser = { name: 'Jane Smith', email: '[email protected]' };
const createdUser = { id: 2, ...newUser };
mockedAxios.post.mockResolvedValue({ data: createdUser });
const result = await userService.createUser(newUser);
expect(mockedAxios.post).toHaveBeenCalledWith('/api/users', newUser);
expect(result).toEqual(createdUser);
});
test('should handle API errors', async () => {
const errorMessage = 'Network Error';
mockedAxios.get.mockRejectedValue(new Error(errorMessage));
await expect(userService.getUser(1)).rejects.toThrow(errorMessage);
expect(mockedAxios.get).toHaveBeenCalledWith('/api/users/1');
});
});
Snapshot Testing
// component.js
function renderUserProfile(user) {
return {
type: 'div',
props: {
className: 'user-profile',
children: [
{
type: 'h2',
props: { children: user.name }
},
{
type: 'p',
props: { children: `Email: ${user.email}` }
},
{
type: 'p',
props: { children: `Age: ${user.age}` }
}
]
}
};
}
module.exports = { renderUserProfile };
// component.test.js
const { renderUserProfile } = require('./component');
describe('UserProfile component', () => {
test('renders user profile correctly', () => {
const user = {
name: 'John Doe',
email: '[email protected]',
age: 30
};
const result = renderUserProfile(user);
// Snapshot file is created on first run
expect(result).toMatchSnapshot();
});
test('renders user profile with inline snapshot', () => {
const user = {
name: 'Jane Smith',
email: '[email protected]',
age: 25
};
const result = renderUserProfile(user);
// Inline snapshot
expect(result).toMatchInlineSnapshot(`
{
"props": {
"children": [
{
"props": {
"children": "Jane Smith",
},
"type": "h2",
},
{
"props": {
"children": "Email: [email protected]",
},
"type": "p",
},
{
"props": {
"children": "Age: 25",
},
"type": "p",
},
],
"className": "user-profile",
},
"type": "div",
}
`);
});
});
Asynchronous Testing
// async-service.js
class AsyncService {
async fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url.includes('error')) {
reject(new Error('Failed to fetch data'));
} else {
resolve({ url, data: 'sample data', timestamp: Date.now() });
}
}, 100);
});
}
async processMultipleRequests(urls) {
const results = await Promise.all(
urls.map(url => this.fetchData(url))
);
return results;
}
async fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await this.fetchData(url);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 50));
}
}
}
}
module.exports = AsyncService;
// async-service.test.js
const AsyncService = require('./async-service');
describe('AsyncService', () => {
let service;
beforeEach(() => {
service = new AsyncService();
});
// Method 1: Return Promise
test('fetches data successfully (Promise return)', () => {
return service.fetchData('https://api.example.com/data')
.then(result => {
expect(result).toHaveProperty('url');
expect(result).toHaveProperty('data', 'sample data');
expect(result).toHaveProperty('timestamp');
});
});
// Method 2: Using async/await
test('fetches data successfully (async/await)', async () => {
const result = await service.fetchData('https://api.example.com/data');
expect(result).toHaveProperty('url', 'https://api.example.com/data');
expect(result.data).toBe('sample data');
expect(typeof result.timestamp).toBe('number');
});
// Method 3: Using .resolves matcher
test('fetches data successfully (.resolves)', () => {
return expect(service.fetchData('https://api.example.com/data'))
.resolves.toMatchObject({
url: 'https://api.example.com/data',
data: 'sample data'
});
});
// Method 4: Combining async/await + .resolves
test('fetches data successfully (async + .resolves)', async () => {
await expect(service.fetchData('https://api.example.com/data'))
.resolves.toHaveProperty('data', 'sample data');
});
// Error case testing
test('handles fetch errors (.rejects)', () => {
return expect(service.fetchData('https://api.example.com/error'))
.rejects.toThrow('Failed to fetch data');
});
test('handles fetch errors (async/await)', async () => {
await expect(service.fetchData('https://api.example.com/error'))
.rejects.toThrow('Failed to fetch data');
});
// Multiple async operations
test('processes multiple requests', async () => {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
const results = await service.processMultipleRequests(urls);
expect(results).toHaveLength(3);
results.forEach((result, index) => {
expect(result.url).toBe(urls[index]);
expect(result.data).toBe('sample data');
});
});
// Testing retry functionality
test('retries on failure', async () => {
// Spy on fetchData to verify call count
const fetchSpy = jest.spyOn(service, 'fetchData')
.mockRejectedValueOnce(new Error('First attempt failed'))
.mockRejectedValueOnce(new Error('Second attempt failed'))
.mockResolvedValueOnce({ url: 'test', data: 'success', timestamp: Date.now() });
const result = await service.fetchWithRetry('https://api.example.com/unreliable');
expect(fetchSpy).toHaveBeenCalledTimes(3);
expect(result.data).toBe('success');
fetchSpy.mockRestore();
});
});
Coverage Collection
// calculator.js
class Calculator {
add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Both arguments must be numbers');
}
return a + b;
}
subtract(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Both arguments must be numbers');
}
return a - b;
}
multiply(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Both arguments must be numbers');
}
return a * b;
}
divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Both arguments must be numbers');
}
if (b === 0) {
throw new Error('Division by zero is not allowed');
}
return a / b;
}
power(base, exponent) {
if (typeof base !== 'number' || typeof exponent !== 'number') {
throw new Error('Both arguments must be numbers');
}
return Math.pow(base, exponent);
}
// This function is not tested (will appear in coverage)
complexCalculation(x) {
if (x > 0) {
return x * 2;
} else if (x < 0) {
return x / 2;
} else {
return 0;
}
}
}
module.exports = Calculator;
// calculator.test.js
const Calculator = require('./calculator');
describe('Calculator', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe('add method', () => {
test('adds two positive numbers', () => {
expect(calculator.add(2, 3)).toBe(5);
});
test('adds positive and negative numbers', () => {
expect(calculator.add(5, -3)).toBe(2);
});
test('throws error for non-number arguments', () => {
expect(() => calculator.add('2', 3)).toThrow('Both arguments must be numbers');
expect(() => calculator.add(2, '3')).toThrow('Both arguments must be numbers');
});
});
describe('subtract method', () => {
test('subtracts two numbers', () => {
expect(calculator.subtract(5, 3)).toBe(2);
});
test('throws error for non-number arguments', () => {
expect(() => calculator.subtract('5', 3)).toThrow('Both arguments must be numbers');
});
});
describe('multiply method', () => {
test('multiplies two numbers', () => {
expect(calculator.multiply(4, 3)).toBe(12);
});
test('throws error for non-number arguments', () => {
expect(() => calculator.multiply(4, 'x')).toThrow('Both arguments must be numbers');
});
});
describe('divide method', () => {
test('divides two numbers', () => {
expect(calculator.divide(6, 2)).toBe(3);
});
test('throws error for division by zero', () => {
expect(() => calculator.divide(5, 0)).toThrow('Division by zero is not allowed');
});
test('throws error for non-number arguments', () => {
expect(() => calculator.divide('6', 2)).toThrow('Both arguments must be numbers');
});
});
describe('power method', () => {
test('calculates power correctly', () => {
expect(calculator.power(2, 3)).toBe(8);
expect(calculator.power(5, 0)).toBe(1);
});
test('throws error for non-number arguments', () => {
expect(() => calculator.power('2', 3)).toThrow('Both arguments must be numbers');
});
});
// complexCalculation method is not tested (will appear as uncovered in coverage report)
});
// package.json (Coverage configuration example)
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage",
"test:coverage:watch": "jest --coverage --watchAll"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx}",
"!src/index.js",
"!src/**/*.test.{js,jsx}"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
},
"coverageReporters": ["text", "lcov", "html"]
}
}
# Coverage execution command
npm run test:coverage
# Output example:
# ----------------------|---------|----------|---------|---------|-------------------
# File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
# ----------------------|---------|----------|---------|---------|-------------------
# All files | 90.48 | 75 | 83.33 | 90.48 |
# calculator.js | 90.48 | 75 | 83.33 | 90.48 | 35,37,39
# ----------------------|---------|----------|---------|---------|-------------------