Jest Testing Guide | Unit Tests, Mocks, Snapshots, Coverage & React
이 글의 핵심
This guide walks through Jest from first test to coverage thresholds and React component tests. It also explains how the runner schedules work, how module mocking is hoisted, how Istanbul instruments code for coverage, and how teams combine unit tests with staging E2E checks before production.
Introduction
Jest is a batteries-included test runner for JavaScript and TypeScript. This article mirrors the practical flow of the Korean jest-testing-guide post—installation, matchers, async tests, mocks, React Testing Library, snapshots, and coverage—then adds internals so you can debug odd failures and design a test strategy that survives CI and release pipelines.
For a longer reference with more patterns, see the Jest Complete Guide.
Test runner architecture (how Jest executes your suite)
At a high level, Jest:
- Discovers test files using
testMatch/testRegexand optionalprojects. - Builds a dependency graph (via
jest-haste-map) so it knows which files to transform and cache. - Spawns worker processes (
jest-worker) to run test files in parallel (CPU-bound; I/O-bound work still benefits from parallelism up to a point). - Applies transforms (Babel,
ts-jest, orbabel-jest) per file, then loads each test file in an isolated VM context (orjest-environment-jsdom/node).
Implications:
- Flaky order: Tests in different files run in parallel; tests in one file run serially by default. Shared global state breaks under parallel workers—reset mocks and avoid mutating
process.envwithoutsetupFilesdiscipline. - Slow cold start: Large monorepos pay for haste-map and transform caches. Use
--maxWorkersin CI to cap memory; usecacheDirectoryintentionally on CI caches. --watch: Uses file watchers to re-run only affected tests when the graph is known.
Mocking mechanisms
jest.fn() — call tracking
jest.fn() creates a function that records every invocation, arguments, return values, and thrown errors. Use it to stub callbacks and verify collaboration between units.
jest.mock('module') — module substitution
jest.mock replaces a module with an auto-mocked version or a factory you provide. Jest hoists these calls: the mock is registered before the rest of the module executes, which surprises newcomers when imports already resolved.
Patterns:
- Partial mock: spread
jest.requireActualinside the factory and override one export. - Manual mocks: place
__mocks__adjacent tonode_modulesor next to the module for predictable replacements in large codebases.
jest.spyOn — observe without replacing
jest.spyOn(obj, 'method') wraps a real method: you can assert calls and optionally mockImplementation or mockRestore() to return to the original.
Timers and environment
jest.useFakeTimers() replaces setTimeout / Date with simulated time—essential for debounce logic. Pair with jest.advanceTimersByTime and remember to jest.useRealTimers() in afterEach when tests mix real and fake time.
Coverage instrumentation
When you run jest --coverage, Jest uses Istanbul (via babel-plugin-istanbul or equivalent) to insert counters into branches, functions, lines, and statements. The report answers “what ran?”—not “what is correct?”.
Configure meaningful gates:
// jest.config.js
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,ts,tsx}',
'!src/**/*.test.{js,ts,tsx}',
'!src/index.tsx',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
Exclude generated files, barrel index files that only re-export, and pure types—otherwise thresholds become theater.
Production-oriented testing patterns
Jest itself does not run inside your production servers for user traffic. “Production testing” usually means:
- CI on every merge:
jest --ci --coverage --maxWorkers=2with deterministic seeds where applicable. - Staging E2E: Playwright or Cypress against a deployed preview—user journeys and cross-service contracts.
- Synthetic monitoring: scheduled probes from outside (ping, API checks, browser scripts)—orthogonal to Jest but part of the same quality story.
- Feature flags + canary: route a slice of traffic after unit/integration/E2E gates pass.
Treat unit tests as fast feedback on pure logic, integration tests on module boundaries (DB, HTTP with test containers), and E2E as the last line before customers—each layer catches different failure modes.
Install and minimal config
npm install -D jest @types/jest
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
module.exports = {
testEnvironment: 'node',
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.{js,ts}', '!src/**/*.test.{js,ts}'],
};
Matchers, async tests, mocks, RTL, snapshots
The Korean companion post walks through matchers (toEqual, toThrow), async/await tests, jest.mock for modules, React Testing Library examples, and toMatchSnapshot() for components. The patterns are identical in English codebases—prefer roles and labels from Testing Library over CSS selectors.
Checklist
- Install Jest and a transform (
ts-jestor Babel) for TypeScript if needed. - Stabilize mocks (
clearMocks/resetMocks) and avoid shared mutable singletons across files. - Set realistic coverage thresholds on
srconly. - Wire
test:ciinto GitHub Actions (or your CI) with caching fornode_modulesand Jest cache.
Related reading
- Vitest Complete Guide — Vite-native alternative with a similar API.
- React Testing Library Guide — user-centric component queries.