Day 1: Welcome to Jest
What You'll Learn Today
- What testing is and why it matters
- The testing pyramid concept
- What Jest is and its key features
- Installing and setting up Jest
- Writing and running your first test
Why Testing Matters
You write code, open the browser, click around, fill in forms, and check the results. Then you do it again every time you make a change. This "manual testing" approach has serious limitations.
flowchart LR
subgraph Manual["Manual Testing"]
M1["Write code"]
M2["Check in browser"]
M3["Verify other features"]
M4["Miss regressions"]
end
subgraph Auto["Automated Testing"]
A1["Write code"]
A2["Run tests"]
A3["All features checked"]
A4["Issues caught instantly"]
end
M1 --> M2 --> M3 --> M4
A1 --> A2 --> A3 --> A4
style Manual fill:#ef4444,color:#fff
style Auto fill:#22c55e,color:#fff
| Manual Testing Problem | Automated Testing Solution |
|---|---|
| Repeating the same steps every time | One command runs all checks |
| Easy to miss edge cases | Every test case runs systematically |
| Fear of breaking existing features | Automatic verification after every change |
| Different team members test differently | Test code serves as a living specification |
If you've worked through "Learn JavaScript in 10 Days" or "Learn React in 10 Days," you've experienced the anxiety of adding features and wondering if you broke something. Automated testing eliminates that worry.
The Testing Pyramid
Software testing comes in multiple levels, each serving a different purpose. The testing pyramid illustrates this hierarchy.
flowchart TB
subgraph Pyramid["Testing Pyramid"]
E2E["E2E Tests\n(Few Β· Slow Β· Expensive)"]
INT["Integration Tests\n(Moderate)"]
UNIT["Unit Tests\n(Many Β· Fast Β· Cheap)"]
end
E2E --> INT --> UNIT
style E2E fill:#ef4444,color:#fff
style INT fill:#f59e0b,color:#fff
style UNIT fill:#22c55e,color:#fff
| Test Type | Scope | Speed | Example |
|---|---|---|---|
| Unit Tests | Individual functions/components | Very fast | Does add(2, 3) return 5? |
| Integration Tests | Multiple modules working together | Moderate | Can we fetch and display API data? |
| E2E Tests | Entire application | Slow | Can a user log in and purchase an item? |
Pyramid Principles
- Write the most unit tests: They're fast, stable, and pinpoint the source of failures
- Keep E2E tests focused: They're slow and expensive to maintain
- Each layer complements the others: Unit tests alone can't catch every issue
Series connection: "Learn Cypress in 10 Days" and "Learn Playwright in 10 Days" covered E2E testing. Jest handles the unit tests and integration tests that form the pyramid's foundation.
What Is Jest?
Jest is a JavaScript testing framework developed by Meta (formerly Facebook). It works with React, Vue, Angular, Node.js, and any JavaScript project.
Key Features
flowchart TB
subgraph Jest["Jest Core Features"]
F1["Zero Config"]
F2["Parallel Execution"]
F3["Built-in Mocking"]
F4["Snapshot Testing"]
F5["Code Coverage"]
F6["Watch Mode"]
end
style Jest fill:#99425b,color:#fff
| Feature | Description |
|---|---|
| Zero Config | Works out of the box with no configuration needed |
| Fast Execution | Runs test files in parallel, re-runs only changed files |
| All-in-One | Test runner, assertions, and mocking built in |
| Snapshot Testing | Records UI output and detects unintended changes |
| Code Coverage | Generates coverage reports with no extra tools |
| Watch Mode | Watches for file changes and re-runs related tests |
Why Choose Jest?
Other JavaScript testing frameworks exist (Mocha, Vitest, Jasmine). Here's why Jest is widely adopted.
| Framework | Characteristic | Difference from Jest |
|---|---|---|
| Mocha | Highly flexible | Assertions and mocking require separate packages |
| Vitest | Vite-based, fast | Designed for Vite projects. Jest-compatible API |
| Jasmine | Inspired Jest's design | Fewer features than Jest |
Jest follows the batteries-included philosophy. The test runner, assertion library, mocking library, and coverage tool are all in a single packageβideal for getting started quickly.
Installing and Setting Up Jest
Prerequisites
- Node.js 18 or later
- npm available on your system
Creating a Project
# Create a project directory
mkdir my-jest-project
cd my-jest-project
# Initialize package.json
npm init -y
# Install Jest
npm install --save-dev jest
Configuring package.json
Add test scripts to the scripts section of package.json.
{
"name": "my-jest-project",
"version": "1.0.0",
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"devDependencies": {
"jest": "^30.0.0"
}
}
TypeScript Setup
For TypeScript projects, install additional packages.
# Install TypeScript-related packages
npm install --save-dev typescript ts-jest @types/jest
# Generate TypeScript config
npx tsc --init
Create a Jest config file (jest.config.js).
/** @type {import('jest').Config} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
For JavaScript-only projects, you don't need
jest.config.js. Jest automatically finds.test.jsand.spec.jsfiles by default.
Writing Your First Test
Creating the Code Under Test
Start with a simple module. Create math.js.
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
module.exports = { add, subtract, multiply, divide };
TypeScript version:
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
export function divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
Creating the Test File
Jest automatically recognizes files matching these patterns as tests.
| Pattern | Example |
|---|---|
*.test.js / *.test.ts |
math.test.js |
*.spec.js / *.spec.ts |
math.spec.js |
Files inside __tests__/ |
__tests__/math.js |
Create math.test.js.
// math.test.js
const { add, subtract, multiply, divide } = require('./math');
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test('subtracts 5 - 3 to equal 2', () => {
expect(subtract(5, 3)).toBe(2);
});
test('multiplies 3 * 4 to equal 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
TypeScript version:
// math.test.ts
import { add, subtract, multiply, divide } from './math';
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
test('subtracts 5 - 3 to equal 2', () => {
expect(subtract(5, 3)).toBe(2);
});
test('multiplies 3 * 4 to equal 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('divides 10 / 2 to equal 5', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
Running the Tests
npm test
Output:
PASS ./math.test.js
β adds 1 + 2 to equal 3 (2 ms)
β subtracts 5 - 3 to equal 2
β multiplies 3 * 4 to equal 12
β divides 10 / 2 to equal 5
β throws error when dividing by zero (1 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 0.5 s
All tests showing green (PASS) means everything works correctly.
Understanding the Test Code
Let's break down the anatomy of a test.
test('adds 1 + 2 to equal 3', () => {
expect(add(1, 2)).toBe(3);
});
flowchart LR
subgraph TestAnatomy["Test Anatomy"]
TEST["test(name, fn)\nTest function"]
EXPECT["expect(value)\nActual value"]
MATCHER["toBe(expected)\nMatcher"]
end
TEST --> EXPECT --> MATCHER
style TEST fill:#3b82f6,color:#fff
style EXPECT fill:#8b5cf6,color:#fff
style MATCHER fill:#22c55e,color:#fff
| Element | Description | Example |
|---|---|---|
test(name, fn) |
Defines a test case. name describes what's being tested |
test('adds 1 + 2 to equal 3', ...) |
expect(value) |
Wraps the actual value to test | expect(add(1, 2)) |
.toBe(expected) |
Verifies the value matches the expectation (a matcher) | .toBe(3) |
What Happens When a Test Fails?
Let's intentionally break a test by changing the add function.
function add(a, b) {
return a - b; // intentional bug
}
Running the tests now produces:
FAIL ./math.test.js
β adds 1 + 2 to equal 3 (3 ms)
β adds 1 + 2 to equal 3
expect(received).toBe(expected) // Object.is equality
Expected: 3
Received: -1
1 | const { add } = require('./math');
2 | test('adds 1 + 2 to equal 3', () => {
> 3 | expect(add(1, 2)).toBe(3);
| ^
4 | });
Jest clearly shows what was expected (Expected: 3) and what was actually received (Received: -1), along with the exact line where the failure occurred.
Watch Mode
During development, watch mode automatically re-runs tests when files change.
npm run test:watch
In watch mode, you can use these keyboard shortcuts.
| Key | Action |
|---|---|
a |
Run all tests |
f |
Run only failed tests |
p |
Filter by filename |
t |
Filter by test name |
q |
Quit watch mode |
flowchart TB
subgraph Watch["Watch Mode Flow"]
EDIT["Edit and save file"]
DETECT["Jest detects changes"]
RUN["Related tests run automatically"]
RESULT["Results displayed instantly"]
end
EDIT --> DETECT --> RUN --> RESULT --> EDIT
style Watch fill:#3b82f6,color:#fff
Project Structure Best Practices
There are two common patterns for organizing test files.
Pattern 1: Co-located tests (recommended)
src/
βββ math.js
βββ math.test.js
βββ utils/
β βββ format.js
β βββ format.test.js
βββ components/
βββ Button.jsx
βββ Button.test.jsx
Pattern 2: Centralized tests directory
src/
βββ math.js
βββ utils/
β βββ format.js
βββ components/
β βββ Button.jsx
βββ __tests__/
βββ math.test.js
βββ format.test.js
βββ Button.test.jsx
| Pattern | Pros | Cons |
|---|---|---|
| Co-located | Clear mapping between source and test | More files in each directory |
| tests | Tests grouped in one place | Harder to see which source file each test covers |
Most projects prefer Pattern 1. Having the test right next to the source file makes tests easy to discover and maintain.
Summary
| Concept | Description |
|---|---|
| Automated Testing | A mechanism to verify code behavior automatically |
| Testing Pyramid | Unit β Integration β E2E, three-layer structure |
| Jest | Meta's all-in-one JavaScript testing framework |
test() |
Function that defines a test case |
expect().toBe() |
Verifies a value matches the expectation |
| Watch Mode | Automatically re-runs tests when files change |
Key Takeaways
- Automated tests are a safety net that protects code quality
- Jest works out of the box with zero configuration
test()+expect()+ matcher is the fundamental test structure- Watch mode significantly boosts development productivity
Exercises
Exercise 1: Basics
Write tests for the following greet function.
function greet(name) {
return `Hello, ${name}!`;
}
Expected behavior:
greet('Alice')returns'Hello, Alice!'greet('World')returns'Hello, World!'
Exercise 2: Intermediate
Write tests for the following isEven function. Cover multiple cases.
function isEven(num) {
return num % 2 === 0;
}
Challenge
Write tests for the following clamp function. Cover all boundary cases (below min, above max, and within range).
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
References
- Jest Official Documentation
- Jest GitHub Repository
- The Testing Pyramid - Martin Fowler
- Node.js Download
Next up: In Day 2, we'll learn about "Test Structure and Basic Patterns." You'll master describe for grouping tests, beforeEach / afterEach for setup and teardown, and the AAA (Arrange-Act-Assert) pattern for writing maintainable tests!