Learn Jest in 10 DaysDay 1: Welcome to Jest
Chapter 1Learn Jest in 10 Days

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.js and .spec.js files 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

  1. Automated tests are a safety net that protects code quality
  2. Jest works out of the box with zero configuration
  3. test() + expect() + matcher is the fundamental test structure
  4. 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


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!