Summary
Jest is a delightful JavaScript testing framework developed by Meta (Facebook) that focuses on simplicity and developer experience. It provides everything needed for testing JavaScript applications out of the box, including a test runner, assertion library, mocking capabilities, and code coverage reports. Jest is particularly popular for testing React applications but works seamlessly with any JavaScript framework or vanilla JavaScript. Its zero-configuration setup, powerful matchers, snapshot testing, and built-in mocking make it the go-to choice for modern frontend testing. This cheatsheet covers Jest fundamentals, testing patterns, React component testing, and best practices for technical interviews.
Jest Fundamentals
What is Jest?
- JavaScript testing framework by Facebook
- Zero configuration
- Built-in mocking, assertions, and coverage
- Works with React, Vue, Angular, Node.js
Basic Setup
// package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
Basic Test Structure
// math.test.js
describe('Math operations', () => {
test('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});
Common Matchers
Equality Matchers
test('equality matchers', () => {
expect(2 + 2).toBe(4); // Strict equality (===)
expect({name: 'jest'}).toEqual({name: 'jest'}); // Deep equality
expect(null).toBeNull(); // Only matches null
expect(undefined).toBeUndefined(); // Only matches undefined
expect('Jest').toBeDefined(); // Opposite of toBeUndefined
});
Truthiness Matchers
test('truthiness', () => {
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect('').toBeFalsy();
expect('Jest').toBeTruthy();
});
Number Matchers
test('numbers', () => {
expect(2 + 2).toBeGreaterThan(3);
expect(2 + 2).toBeGreaterThanOrEqual(4);
expect(2 + 2).toBeLessThan(5);
expect(2 + 2).toBeLessThanOrEqual(4);
expect(0.1 + 0.2).toBeCloseTo(0.3); // For floating points
});
String Matchers
test('strings', () => {
expect('team').not.toMatch(/I/);
expect('Christoph').toMatch(/stop/);
expect('Hello Jest').toMatch('Jest');
});
Array and Iterable Matchers
test('arrays', () => {
const list = ['React', 'Vue', 'Angular'];
expect(list).toContain('React');
expect(new Set(list)).toContain('Vue');
expect(list).toHaveLength(3);
});
Exception Matchers
test('exceptions', () => {
const throwError = () => {
throw new Error('Wrong!');
};
expect(throwError).toThrow();
expect(throwError).toThrow('Wrong!');
expect(throwError).toThrow(/Wrong/);
});
Test Structure & Lifecycle
Setup and Teardown
// Run before each test
beforeEach(() => {
initializeDatabase();
});
// Run after each test
afterEach(() => {
clearDatabase();
});
// Run once before all tests
beforeAll(() => {
return initializeCity();
});
// Run once after all tests
afterAll(() => {
return clearCity();
});
Scoping
describe('User module', () => {
beforeEach(() => {
// Runs before each test in this describe block
});
describe('Login', () => {
test('should login with valid credentials', () => {
// Test implementation
});
});
});
Skip and Only
test.skip('this test will be skipped', () => {
// This won't run
});
test.only('only this test will run', () => {
// Only this runs in the file
});
Asynchronous Testing
Promises
// Return a promise
test('fetches user data', () => {
return fetchUser().then(data => {
expect(data.name).toBe('John');
});
});
// Using async/await
test('fetches user data', async () => {
const data = await fetchUser();
expect(data.name).toBe('John');
});
Callbacks
test('callback test', done => {
function callback(data) {
try {
expect(data).toBe('data');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
Testing Rejected Promises
test('rejects with error', async () => {
await expect(fetchUser()).rejects.toThrow('User not found');
});
Mocking
Mock Functions
test('mock implementation', () => {
const mockFn = jest.fn();
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(1);
});
Mock Return Values
const mockFn = jest.fn();
mockFn
.mockReturnValueOnce(10)
.mockReturnValueOnce('x')
.mockReturnValue(true);
console.log(mockFn(), mockFn(), mockFn()); // 10, 'x', true
Mock Modules
// Mock an entire module
jest.mock('./api');
// Partial mocking
jest.mock('./config', () => ({
...jest.requireActual('./config'),
apiUrl: 'http://localhost'
}));
Spy on Existing Functions
const video = {
play() {
return true;
}
};
test('spies on video play', () => {
const spy = jest.spyOn(video, 'play');
video.play();
expect(spy).toHaveBeenCalled();
spy.mockRestore(); // Restore original implementation
});
Testing React Components
Basic Component Test
import { render, screen } from '@testing-library/react';
import Button from './Button';
test('renders button with text', () => {
render(<Button>Click me</Button>);
const button = screen.getByText(/click me/i);
expect(button).toBeInTheDocument();
});
Testing User Interactions
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter', () => {
render(<Counter />);
const button = screen.getByText('Increment');
fireEvent.click(button);
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
Testing with React Testing Library
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('user can type in input', async () => {
const user = userEvent.setup();
render(<Input />);
const input = screen.getByRole('textbox');
await user.type(input, 'Hello');
expect(input).toHaveValue('Hello');
});
Testing Hooks
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
Snapshot Testing
Basic Snapshot
test('component snapshot', () => {
const tree = renderer
.create(<Button>Click me</Button>)
.toJSON();
expect(tree).toMatchSnapshot();
});
Inline Snapshots
test('inline snapshot', () => {
expect({name: 'jest'}).toMatchInlineSnapshot(`
Object {
"name": "jest",
}
`);
});
Updating Snapshots
jest --updateSnapshot
# or
jest -u
Coverage
Coverage Configuration
// jest.config.js
module.exports = {
collectCoverage: true,
coverageDirectory: 'coverage',
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
Coverage Report Types
jest --coverage --coverageReporters=text-lcov
jest --coverage --coverageReporters=html
Advanced Patterns
Custom Matchers
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
return {
pass,
message: () => `expected ${received} to be within range ${floor} - ${ceiling}`
};
}
});
test('custom matcher', () => {
expect(100).toBeWithinRange(90, 110);
});
Timer Mocks
jest.useFakeTimers();
test('waits 1 second', () => {
const timerFn = jest.fn();
setTimeout(timerFn, 1000);
expect(timerFn).not.toBeCalled();
jest.advanceTimersByTime(1000);
expect(timerFn).toBeCalled();
});
Manual Mocks
// __mocks__/fs.js
module.exports = {
readFileSync: jest.fn(() => 'mocked content')
};
// In test file
jest.mock('fs');
Testing Error Boundaries
test('error boundary', () => {
const ThrowError = () => {
throw new Error('Test error');
};
// Suppress console.error for this test
const spy = jest.spyOn(console, 'error').mockImplementation();
const { getByText } = render(
<ErrorBoundary>
<ThrowError />
</ErrorBoundary>
);
expect(getByText(/something went wrong/i)).toBeInTheDocument();
spy.mockRestore();
});
Best Practices
1. Test Structure
- Use descriptive test names
- Follow AAA pattern: Arrange, Act, Assert
- One assertion per test when possible
2. Isolation
- Tests should be independent
- Clean up after tests
- Don't rely on test order
3. Mocking
- Mock external dependencies
- Don't mock everything
- Prefer dependency injection
4. Performance
- Use
beforeAllfor expensive setup - Parallelize tests when possible
- Mock heavy operations
5. Maintainability
- Keep tests simple and readable
- Use helper functions for common operations
- Update tests when code changes
Key Interview Concepts
Matcher Comparison
| Matcher | Use Case | Example | Comparison Type |
|---|---|---|---|
toBe |
Primitives, exact reference | expect(1).toBe(1) |
Object.is() (===) |
toEqual |
Objects, arrays, deep equality | expect({a:1}).toEqual({a:1}) |
Deep/recursive |
toStrictEqual |
Like toEqual but checks undefined | expect({a:1}).toStrictEqual({a:1}) |
Strict deep |
toMatchObject |
Partial object matching | expect(obj).toMatchObject({a:1}) |
Subset matching |
Asynchronous Testing Patterns
// Pattern 1: Return Promise
test('async with promise', () => {
return fetchData().then(data => {
expect(data).toBe('data');
});
});
// Pattern 2: Async/Await (Recommended)
test('async with await', async () => {
const data = await fetchData();
expect(data).toBe('data');
});
// Pattern 3: Done Callback (Legacy)
test('async with done', (done) => {
fetchData((data) => {
expect(data).toBe('data');
done();
});
});
Mocking Strategies
| Type | Purpose | Use Case | Example |
|---|---|---|---|
| Mock Function | Replace function behavior | Unit tests | jest.fn() |
| Module Mock | Replace entire module | External dependencies | jest.mock('./api') |
| Spy | Watch existing function | Verify calls | jest.spyOn(obj, 'method') |
| Manual Mock | Custom implementation | Complex mocks | __mocks__/module.js |
| Auto Mock | Automatic module mocking | Quick isolation | jest.createMockFromModule() |
Testing Component Patterns
// Pattern 1: Testing Library (Recommended)
import { render, screen } from '@testing-library/react';
render(<Component />);
expect(screen.getByRole('button')).toBeInTheDocument();
// Pattern 2: Enzyme (Legacy)
import { shallow, mount } from 'enzyme';
const wrapper = shallow(<Component />);
expect(wrapper.find('button').length).toBe(1);
// Pattern 3: Snapshot Testing
const tree = renderer.create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
Snapshot Testing Best Practices
| Aspect | Good Practice | Bad Practice |
|---|---|---|
| Scope | Small, focused components | Large component trees |
| Content | Stable UI structure | Dynamic/random content |
| Review | Always review changes | Blindly updating |
| Combination | Use with other tests | Rely only on snapshots |
Code Coverage Metrics
| Metric | Description | Target |
|---|---|---|
| Statement | % of statements executed | 80-90% |
| Branch | % of branches (if/else) tested | 75-85% |
| Function | % of functions called | 80-90% |
| Line | % of lines executed | 80-90% |
Testing Pyramid & Jest's Role
/\ E2E Tests (Cypress/Playwright)
/ \ - User journeys
/ \
/------\ Integration Tests (Jest + RTL)
/ Jest \ - Component integration
/ \ - API integration
/------------\
/ Unit Tests \ (Jest)
/________________\ - Functions, hooks, utilities
Redux Testing Strategies
// 1. Test Actions
expect(actions.addTodo('Buy milk')).toEqual({
type: 'ADD_TODO',
payload: 'Buy milk'
});
// 2. Test Reducers
expect(reducer(undefined, { type: 'ADD_TODO' })).toEqual(expectedState);
// 3. Test Connected Components
const store = mockStore({ todos: [] });
render(
<Provider store={store}>
<ConnectedComponent />
</Provider>
);
Test File Organization
Src Structure Test Structure
├── components/ ├── components/
│ ├── Button.jsx │ ├── Button.test.jsx
│ └── Card.jsx │ └── Card.test.jsx
├── utils/ ├── utils/
│ └── helpers.js │ └── helpers.test.js
└── services/ └── services/
└── api.js └── api.test.js
Debugging Techniques
| Method | When to Use | How |
|---|---|---|
console.log |
Quick inspection | Add in test or component |
debugger |
Step through code | Add breakpoint, run with node --inspect |
.only |
Isolate test | test.only('...') |
--watch |
Continuous testing | jest --watch |
| VSCode Debugger | Full debugging | Launch configuration |
--bail |
Stop on first failure | jest --bail |
Quick Reference
Common Commands
jest # Run all tests
jest --watch # Watch mode
jest --coverage # With coverage
jest path/to/test # Run specific test
jest --updateSnapshot # Update snapshots
jest --maxWorkers=4 # Parallel execution
Jest Configuration
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
moduleNameMapper: {
'\\.(css|less|scss)$': 'identity-obj-proxy',
},
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
};
Must-Know Concepts for Interviews
- Test Doubles: Dummy, Stub, Spy, Mock, Fake
- Test Pyramid: Unit → Integration → E2E
- TDD: Red → Green → Refactor
- Coverage Types: Statement, Branch, Function, Line
- Assertion Libraries: Jest assertions, expect, chai
- React Testing: RTL queries, user interactions, async updates
- Performance: Parallel execution, test.concurrent
- Debugging: VSCode Jest extension, Chrome DevTools
Interview Preparation Checklist
- ✅ Matchers: toBe vs toEqual, custom matchers
- ✅ Async Testing: Promises, async/await, timers
- ✅ Mocking: Functions, modules, spies
- ✅ React Testing: RTL, user events, hooks
- ✅ Snapshots: When to use, updating, best practices
- ✅ Coverage: Metrics, thresholds, reports
- ✅ Test Organization: Structure, naming, AAA pattern
- ✅ Performance: Parallel testing, focused tests
- ✅ Debugging: Tools and techniques
- ✅ Best Practices: FIRST principles, isolation
Key Takeaway: Jest excels at making testing enjoyable and productive. Focus on writing tests that are Fast, Independent, Repeatable, Self-validating, and Timely (FIRST). Remember that good tests document behavior, catch regressions, and enable confident refactoring.