Skip to main content

Unit Testing with Jest

Created: December 18, 2025 5 min read

Jest is a popular JavaScript testing framework that makes writing and running tests easy and enjoyable. See Javascript Guide for more context. See Javascript Guide for more context.

Installation and Setup

Install Jest

npm install --save-dev jest

Configure package.json

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

Create Test File

// math.test.js
describe("Math operations", () => {
    test("adds two numbers", () => {
        expect(2 + 2).toBe(4);
    });
});

Run Tests

npm test

Basic Test Structure

Test Syntax

test("description", () => {
    // Test code
});

// Or using it()
it("description", () => {
    // Test code
});

Describe Blocks

describe("Calculator", () => {
    test("adds numbers", () => {
        expect(add(2, 3)).toBe(5);
    });
    
    test("subtracts numbers", () => {
        expect(subtract(5, 3)).toBe(2);
    });
});

Matchers

Equality

test("equality matchers", () => {
    expect(4).toBe(4); // Strict equality
    expect({ a: 1 }).toEqual({ a: 1 }); // Deep equality
    expect(null).toBeNull();
    expect(undefined).toBeUndefined();
    expect(true).toBeDefined();
});

Truthiness

test("truthiness", () => {
    expect(true).toBeTruthy();
    expect(false).toBeFalsy();
    expect(1).toBeTruthy();
    expect(0).toBeFalsy();
});

Numbers

test("number matchers", () => {
    expect(4).toBeGreaterThan(3);
    expect(3).toBeGreaterThanOrEqual(3);
    expect(2).toBeLessThan(3);
    expect(3).toBeLessThanOrEqual(3);
    expect(0.1 + 0.2).toBeCloseTo(0.3);
});

Strings

test("string matchers", () => {
    expect("hello").toMatch(/ell/);
    expect("hello").toMatch("ell");
    expect("hello").toContain("ell");
});

Arrays and Objects

test("array and object matchers", () => {
    expect([1, 2, 3]).toContain(2);
    expect([1, 2, 3]).toHaveLength(3);
    expect({ a: 1 }).toHaveProperty("a");
    expect({ a: 1 }).toHaveProperty("a", 1);
});

Exceptions

test("exception matchers", () => {
    expect(() => {
        throw new Error("Test error");
    }).toThrow();
    
    expect(() => {
        throw new Error("Test error");
    }).toThrow("Test error");
    
    expect(() => {
        throw new Error("Test error");
    }).toThrow(Error);
});

Setup and Teardown

beforeEach and afterEach

describe("Database", () => {
    let db;
    
    beforeEach(() => {
        db = new Database();
        db.connect();
    });
    
    afterEach(() => {
        db.disconnect();
    });
    
    test("saves data", () => {
        db.save("key", "value");
        expect(db.get("key")).toBe("value");
    });
});

beforeAll and afterAll

describe("API", () => {
    let server;
    
    beforeAll(() => {
        server = startServer();
    });
    
    afterAll(() => {
        server.stop();
    });
    
    test("fetches data", async () => {
        const data = await fetch("/api/data");
        expect(data).toBeDefined();
    });
});

Practical Examples

Testing Functions

// math.js
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

// math.test.js
import { add, multiply } from "./math";

describe("Math functions", () => {
    test("add returns sum", () => {
        expect(add(2, 3)).toBe(5);
        expect(add(-1, 1)).toBe(0);
        expect(add(0, 0)).toBe(0);
    });
    
    test("multiply returns product", () => {
        expect(multiply(2, 3)).toBe(6);
        expect(multiply(-2, 3)).toBe(-6);
        expect(multiply(0, 5)).toBe(0);
    });
});

Testing Classes

// User.js
export class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
    
    getInfo() {
        return `${this.name} (${this.email})`;
    }
    
    isValidEmail() {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email);
    }
}

// User.test.js
import { User } from "./User";

describe("User class", () => {
    let user;
    
    beforeEach(() => {
        user = new User("Alice", "[email protected]");
    });
    
    test("creates user with name and email", () => {
        expect(user.name).toBe("Alice");
        expect(user.email).toBe("[email protected]");
    });
    
    test("getInfo returns formatted string", () => {
        expect(user.getInfo()).toBe("Alice ([email protected])");
    });
    
    test("isValidEmail validates email", () => {
        expect(user.isValidEmail()).toBe(true);
        
        user.email = "invalid";
        expect(user.isValidEmail()).toBe(false);
    });
});

Testing Async Functions

// api.js
export async function fetchUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}

// api.test.js
import { fetchUser } from "./api";

describe("API functions", () => {
    test("fetchUser returns user data", async () => {
        const user = await fetchUser(1);
        expect(user).toHaveProperty("id");
        expect(user).toHaveProperty("name");
    });
});

Testing with Mocks

// database.js
export class Database {
    async save(key, value) {
        // Actual database operation
    }
}

// service.js
export class UserService {
    constructor(db) {
        this.db = db;
    }
    
    async createUser(name, email) {
        const user = { name, email };
        await this.db.save("user", user);
        return user;
    }
}

// service.test.js
import { UserService } from "./service";

describe("UserService", () => {
    test("createUser saves to database", async () => {
        const mockDb = {
            save: jest.fn()
        };
        
        const service = new UserService(mockDb);
        const user = await service.createUser("Alice", "[email protected]");
        
        expect(mockDb.save).toHaveBeenCalledWith("user", user);
        expect(mockDb.save).toHaveBeenCalledTimes(1);
    });
});

Testing with Spies

// calculator.js
export class Calculator {
    add(a, b) {
        return a + b;
    }
    
    calculate(a, b, operation) {
        return operation(a, b);
    }
}

// calculator.test.js
import { Calculator } from "./calculator";

describe("Calculator", () => {
    test("calculate calls operation", () => {
        const calc = new Calculator();
        const spy = jest.spyOn(calc, "add");
        
        calc.calculate(2, 3, calc.add);
        
        expect(spy).toHaveBeenCalledWith(2, 3);
        spy.mockRestore();
    });
});

Test Coverage

Generate Coverage Report

npm run test:coverage

Coverage Thresholds

{
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.js",
      "!src/index.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

Best Practices

Write Descriptive Test Names

// Good
test("returns sum of two positive numbers", () => {
    expect(add(2, 3)).toBe(5);
});

// Avoid
test("add works", () => {
    expect(add(2, 3)).toBe(5);
});

Test One Thing Per Test

// Good - focused test
test("add returns correct sum", () => {
    expect(add(2, 3)).toBe(5);
});

// Avoid - testing multiple things
test("add and multiply work", () => {
    expect(add(2, 3)).toBe(5);
    expect(multiply(2, 3)).toBe(6);
});

Use Arrange-Act-Assert Pattern

test("user can login", () => {
    // Arrange
    const user = new User("alice", "password123");
    
    // Act
    const result = user.login("alice", "password123");
    
    // Assert
    expect(result).toBe(true);
});

Mock External Dependencies

// Good - mock external API
jest.mock("./api");

test("service fetches data", async () => {
    const mockData = { id: 1, name: "Alice" };
    api.fetchUser.mockResolvedValue(mockData);
    
    const result = await service.getUser(1);
    expect(result).toEqual(mockData);
});

Summary

  • Jest: JavaScript testing framework
  • test(): define a test
  • expect(): make assertions
  • Matchers: toBe(), toEqual(), toContain(), etc.
  • Setup/Teardown: beforeEach(), afterEach()
  • Mocks: jest.fn(), jest.mock()
  • Spies: jest.spyOn()
  • Coverage: measure code coverage
  • Best practice: write focused, descriptive tests

Official Documentation

Next Steps

  1. Test Fixtures and Setup/Teardown
  2. Mocking and Spying with Jest
  3. Code Coverage and Quality Metrics

Resources

Comments

Share this article

Scan to read on mobile