Learn Jest in 10 DaysDay 3: Mastering Matchers
books.chapter 3Learn Jest in 10 Days

Day 3: Mastering Matchers

What You'll Learn Today

  • The difference between toBe and toEqual
  • Truthiness matchers (toBeTruthy, toBeFalsy, toBeNull, etc.)
  • Number matchers (toBeGreaterThan, toBeCloseTo, etc.)
  • String matchers (toMatch, toContain)
  • Array and object matchers
  • Exception matchers (toThrow)
  • Negation with not

What Are Matchers?

Matchers are verification methods chained after expect(). The toBe you used in Day 1 is one matcher.

expect(actual).matcher(expected);
flowchart LR
    subgraph MatcherFlow["Matcher Flow"]
        E["expect(actual value)"]
        M["matcher(expected value)"]
        R["Pass or Fail"]
    end
    E --> M --> R
    style E fill:#8b5cf6,color:#fff
    style M fill:#3b82f6,color:#fff
    style R fill:#22c55e,color:#fff

Jest provides many matchers for different purposes. Choosing the right matcher makes your test intent clear and produces better error messages.


Equality: toBe vs toEqual

toBe β€” Strict Equality (===)

toBe uses JavaScript's === strict equality. Use it for primitive values.

test('toBe compares with strict equality', () => {
  expect(1 + 2).toBe(3);
  expect('hello').toBe('hello');
  expect(true).toBe(true);
  expect(null).toBe(null);
  expect(undefined).toBe(undefined);
});

toEqual β€” Deep Equality

toEqual recursively compares the contents of objects and arrays.

test('toEqual compares object contents', () => {
  const user = { name: 'Alice', age: 25 };

  // βœ… toEqual: compares contents
  expect(user).toEqual({ name: 'Alice', age: 25 });

  // ❌ toBe: fails because they are different object references
  // expect(user).toBe({ name: 'Alice', age: 25 });
});

test('toEqual works with nested objects', () => {
  const data = {
    user: { name: 'Alice' },
    scores: [90, 85, 92],
  };

  expect(data).toEqual({
    user: { name: 'Alice' },
    scores: [90, 85, 92],
  });
});

toStrictEqual β€” Stricter Deep Equality

toStrictEqual also checks for undefined properties and object classes.

test('toStrictEqual checks for undefined properties', () => {
  // toEqual: passes (undefined properties are ignored)
  expect({ name: 'Alice', age: undefined }).toEqual({ name: 'Alice' });

  // toStrictEqual: fails (undefined property exists)
  // expect({ name: 'Alice', age: undefined }).toStrictEqual({ name: 'Alice' });
});
Matcher Comparison Use For
toBe === (reference equality) Primitives (numbers, strings, booleans)
toEqual Deep equality Object and array contents
toStrictEqual Stricter deep equality When undefined properties matter
flowchart TB
    subgraph Comparison["Choosing an Equality Matcher"]
        Q1{"What are you comparing?"}
        P["Primitives\n(numbers, strings, booleans)"]
        O["Objects / Arrays"]
        S{"Check undefined\nproperties?"}
        TBE["toBe"]
        TEQ["toEqual"]
        TSE["toStrictEqual"]
    end
    Q1 -->|Primitive| P --> TBE
    Q1 -->|Object/Array| O --> S
    S -->|No| TEQ
    S -->|Yes| TSE
    style TBE fill:#3b82f6,color:#fff
    style TEQ fill:#8b5cf6,color:#fff
    style TSE fill:#22c55e,color:#fff

Truthiness Matchers

These matchers test JavaScript's "truthy / falsy" values.

describe('truthiness matchers', () => {
  test('toBeNull matches only null', () => {
    expect(null).toBeNull();
  });

  test('toBeUndefined matches only undefined', () => {
    expect(undefined).toBeUndefined();
  });

  test('toBeDefined matches anything that is not undefined', () => {
    expect('hello').toBeDefined();
    expect(0).toBeDefined();
    expect(null).toBeDefined();
  });

  test('toBeTruthy matches truthy values', () => {
    expect('hello').toBeTruthy();
    expect(1).toBeTruthy();
    expect([]).toBeTruthy();
    expect({}).toBeTruthy();
  });

  test('toBeFalsy matches falsy values', () => {
    expect(0).toBeFalsy();
    expect('').toBeFalsy();
    expect(null).toBeFalsy();
    expect(undefined).toBeFalsy();
    expect(false).toBeFalsy();
    expect(NaN).toBeFalsy();
  });
});
Matcher Matches
toBeNull() Only null
toBeUndefined() Only undefined
toBeDefined() Anything except undefined
toBeTruthy() Values that evaluate to true in an if
toBeFalsy() Values that evaluate to false in an if

Recap: JavaScript's falsy values are false, 0, "", null, undefined, and NaN (see "Learn JavaScript in 10 Days" Day 4).


Number Matchers

Matchers for comparing numbers and handling floating-point precision.

describe('number matchers', () => {
  test('comparison matchers', () => {
    const value = 10;

    expect(value).toBeGreaterThan(9);
    expect(value).toBeGreaterThanOrEqual(10);
    expect(value).toBeLessThan(11);
    expect(value).toBeLessThanOrEqual(10);
  });

  test('toBeCloseTo for floating point', () => {
    // ❌ floating point issue
    // expect(0.1 + 0.2).toBe(0.3);

    // βœ… use toBeCloseTo for floating point comparison
    expect(0.1 + 0.2).toBeCloseTo(0.3);
    expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // 5 decimal places
  });
});

TypeScript version:

describe('number matchers', () => {
  test('comparison matchers', () => {
    const value: number = 10;

    expect(value).toBeGreaterThan(9);
    expect(value).toBeGreaterThanOrEqual(10);
    expect(value).toBeLessThan(11);
    expect(value).toBeLessThanOrEqual(10);
  });

  test('toBeCloseTo for floating point', () => {
    expect(0.1 + 0.2).toBeCloseTo(0.3);
  });
});
Matcher Meaning
toBeGreaterThan(n) > n
toBeGreaterThanOrEqual(n) >= n
toBeLessThan(n) < n
toBeLessThanOrEqual(n) <= n
toBeCloseTo(n, digits?) Approximate floating-point comparison

Why toBeCloseTo? In JavaScript, 0.1 + 0.2 equals 0.30000000000000004. Always use toBeCloseTo instead of toBe for floating-point results.


String Matchers

describe('string matchers', () => {
  test('toMatch with regular expression', () => {
    expect('hello world').toMatch(/world/);
    expect('hello world').toMatch(/^hello/);
    expect('user@example.com').toMatch(/^[\w.]+@[\w.]+\.\w+$/);
  });

  test('toMatch with string', () => {
    expect('hello world').toMatch('world');
    expect('JavaScript is awesome').toMatch('awesome');
  });

  test('toContain with strings', () => {
    expect('hello world').toContain('world');
  });

  test('toHaveLength for string length', () => {
    expect('hello').toHaveLength(5);
    expect('').toHaveLength(0);
  });
});
Matcher Use For
toMatch(regexp | string) Match a regex or substring
toContain(string) Contains a substring
toHaveLength(n) Verify string length

Array Matchers

describe('array matchers', () => {
  const fruits = ['apple', 'banana', 'cherry'];

  test('toContain checks if array includes an item', () => {
    expect(fruits).toContain('banana');
  });

  test('toHaveLength checks array length', () => {
    expect(fruits).toHaveLength(3);
  });

  test('toEqual compares array contents', () => {
    expect(fruits).toEqual(['apple', 'banana', 'cherry']);
  });

  test('toContainEqual checks object in array', () => {
    const users = [
      { name: 'Alice', age: 25 },
      { name: 'Bob', age: 30 },
    ];

    expect(users).toContainEqual({ name: 'Alice', age: 25 });
  });
});

TypeScript version:

describe('array matchers', () => {
  const fruits: string[] = ['apple', 'banana', 'cherry'];

  test('toContain checks if array includes an item', () => {
    expect(fruits).toContain('banana');
  });

  test('toHaveLength checks array length', () => {
    expect(fruits).toHaveLength(3);
  });

  test('toContainEqual checks object in array', () => {
    const users: Array<{ name: string; age: number }> = [
      { name: 'Alice', age: 25 },
      { name: 'Bob', age: 30 },
    ];

    expect(users).toContainEqual({ name: 'Alice', age: 25 });
  });
});
Matcher Use For
toContain(item) Array includes a primitive value
toContainEqual(obj) Array includes an object (deep equality)
toHaveLength(n) Array length
toEqual(arr) Compare entire array contents

Object Matchers

describe('object matchers', () => {
  const user = {
    name: 'Alice',
    age: 25,
    email: 'alice@example.com',
    address: {
      city: 'Tokyo',
      country: 'Japan',
    },
  };

  test('toHaveProperty checks for a property', () => {
    expect(user).toHaveProperty('name');
    expect(user).toHaveProperty('name', 'Alice');
  });

  test('toHaveProperty with nested path', () => {
    expect(user).toHaveProperty('address.city', 'Tokyo');
  });

  test('toMatchObject checks partial match', () => {
    expect(user).toMatchObject({
      name: 'Alice',
      age: 25,
    });
    // email and address are not checked
  });

  test('toEqual checks full match', () => {
    expect(user).toEqual({
      name: 'Alice',
      age: 25,
      email: 'alice@example.com',
      address: {
        city: 'Tokyo',
        country: 'Japan',
      },
    });
  });
});
Matcher Use For
toHaveProperty(key, value?) Check a specific property exists (optionally with value)
toMatchObject(obj) Partial match of object properties
toEqual(obj) Full match of all properties
flowchart TB
    subgraph ObjMatch["Choosing an Object Matcher"]
        Q{"What to verify?"}
        PROP["A specific property"]
        PARTIAL["Some properties"]
        FULL["All properties"]
        HP["toHaveProperty"]
        MO["toMatchObject"]
        EQ["toEqual"]
    end
    Q -->|"One property"| PROP --> HP
    Q -->|"Partial check"| PARTIAL --> MO
    Q -->|"Full comparison"| FULL --> EQ
    style HP fill:#3b82f6,color:#fff
    style MO fill:#8b5cf6,color:#fff
    style EQ fill:#22c55e,color:#fff

Exception Matchers

Use toThrow to verify that a function throws an error.

function validateAge(age) {
  if (typeof age !== 'number') {
    throw new TypeError('Age must be a number');
  }
  if (age < 0 || age > 150) {
    throw new RangeError('Age must be between 0 and 150');
  }
  return true;
}

TypeScript version:

function validateAge(age: unknown): boolean {
  if (typeof age !== 'number') {
    throw new TypeError('Age must be a number');
  }
  if (age < 0 || age > 150) {
    throw new RangeError('Age must be between 0 and 150');
  }
  return true;
}
describe('toThrow', () => {
  test('throws on non-number input', () => {
    expect(() => validateAge('twenty')).toThrow();
  });

  test('throws TypeError with specific message', () => {
    expect(() => validateAge('twenty')).toThrow('Age must be a number');
  });

  test('throws specific error type', () => {
    expect(() => validateAge('twenty')).toThrow(TypeError);
  });

  test('throws RangeError for out-of-range values', () => {
    expect(() => validateAge(-1)).toThrow(RangeError);
    expect(() => validateAge(200)).toThrow('Age must be between 0 and 150');
  });

  test('does not throw for valid input', () => {
    expect(() => validateAge(25)).not.toThrow();
  });
});

Important: When using toThrow, you must wrap the function call in an arrow function. Calling it directly would break the test itself.

// ❌ wrong: error breaks the test
expect(validateAge('twenty')).toThrow();

// βœ… correct: wrap in a function
expect(() => validateAge('twenty')).toThrow();
toThrow Usage What It Checks
toThrow() Throws any error
toThrow('message') Error contains specific message
toThrow(ErrorType) Error is of specific type
toThrow(/regex/) Error message matches regex

Negation with not

Every matcher can be negated by prepending .not.

describe('not modifier', () => {
  test('not.toBe', () => {
    expect(1 + 1).not.toBe(3);
  });

  test('not.toEqual', () => {
    expect({ name: 'Alice' }).not.toEqual({ name: 'Bob' });
  });

  test('not.toContain', () => {
    expect(['apple', 'banana']).not.toContain('cherry');
  });

  test('not.toBeNull', () => {
    expect('hello').not.toBeNull();
  });

  test('not.toThrow', () => {
    expect(() => validateAge(25)).not.toThrow();
  });
});

expect.any and expect.anything

Use these to verify values by type or existence rather than exact value.

describe('asymmetric matchers', () => {
  test('expect.any matches any value of a given type', () => {
    const user = { name: 'Alice', age: 25, createdAt: new Date() };

    expect(user).toEqual({
      name: expect.any(String),
      age: expect.any(Number),
      createdAt: expect.any(Date),
    });
  });

  test('expect.anything matches anything except null/undefined', () => {
    const response = { id: 1, data: 'some value' };

    expect(response).toEqual({
      id: expect.anything(),
      data: expect.anything(),
    });
  });

  test('expect.stringContaining', () => {
    expect('hello world').toEqual(expect.stringContaining('world'));
  });

  test('expect.arrayContaining', () => {
    const arr = [1, 2, 3, 4, 5];
    expect(arr).toEqual(expect.arrayContaining([1, 3, 5]));
  });

  test('expect.objectContaining', () => {
    const user = { name: 'Alice', age: 25, email: 'alice@example.com' };

    expect(user).toEqual(expect.objectContaining({
      name: 'Alice',
      age: 25,
    }));
  });
});

TypeScript version:

describe('asymmetric matchers', () => {
  test('expect.any matches any value of a given type', () => {
    const user = { name: 'Alice', age: 25, createdAt: new Date() };

    expect(user).toEqual({
      name: expect.any(String),
      age: expect.any(Number),
      createdAt: expect.any(Date),
    });
  });

  test('expect.objectContaining', () => {
    const user = { name: 'Alice', age: 25, email: 'alice@example.com' };

    expect(user).toEqual(expect.objectContaining({
      name: 'Alice',
      age: 25,
    }));
  });
});
Asymmetric Matcher Use For
expect.any(Constructor) Type-only check (String, Number, Date, etc.)
expect.anything() Anything except null and undefined
expect.stringContaining(str) String contains substring
expect.stringMatching(regexp) String matches regex
expect.arrayContaining(arr) Array includes specified elements (any order)
expect.objectContaining(obj) Object includes specified properties

Matcher Quick Reference

flowchart TB
    subgraph Guide["Matcher Selection Guide"]
        START{"What to verify?"}
        PRIM["Primitive values"]
        OBJ["Objects / Arrays"]
        STR["String patterns"]
        NUM["Number ranges"]
        ERR["Errors"]
        TRUTH["Truthiness"]
    end
    START --> PRIM --> |toBe| R1["toBe(value)"]
    START --> OBJ --> |contents| R2["toEqual / toMatchObject"]
    START --> STR --> |regex| R3["toMatch(regex)"]
    START --> NUM --> |comparison| R4["toBeGreaterThan, etc."]
    START --> ERR --> |exceptions| R5["toThrow()"]
    START --> TRUTH --> |null/undefined| R6["toBeNull, etc."]
    style R1 fill:#3b82f6,color:#fff
    style R2 fill:#8b5cf6,color:#fff
    style R3 fill:#22c55e,color:#fff
    style R4 fill:#f59e0b,color:#fff
    style R5 fill:#ef4444,color:#fff
    style R6 fill:#3b82f6,color:#fff

Summary

Category Matchers Use For
Equality toBe, toEqual, toStrictEqual Value and object comparison
Truthiness toBeTruthy, toBeFalsy, toBeNull Boolean and existence checks
Numbers toBeGreaterThan, toBeCloseTo Range and approximation
Strings toMatch, toContain Pattern and substring matching
Arrays toContain, toContainEqual, toHaveLength Element and length checks
Objects toHaveProperty, toMatchObject Property and partial matching
Exceptions toThrow Error verification
Negation not.matcher() Negate any matcher
Asymmetric expect.any(), expect.objectContaining() Flexible partial matching

Key Takeaways

  1. Use toBe for primitives, toEqual for objects and arrays
  2. Always use toBeCloseTo for floating-point comparisons
  3. Wrap function calls in an arrow function when using toThrow
  4. expect.any() and expect.objectContaining() enable flexible assertions

Exercises

Exercise 1: Basics

Fill in the blanks in the following test.

test('string matchers', () => {
  const message = 'Welcome to Jest testing!';

  expect(message).___('Welcome');          // starts with "Welcome"
  expect(message).___('testing');           // contains "testing"
  expect(message).___(/Jest/);             // matches regex
  expect(message).___('Hello');            // does NOT contain "Hello"
});

Exercise 2: Intermediate

Write tests to verify the following API response. Verify that id is any number and createdAt is any string.

const response = {
  id: 42,
  name: 'Alice',
  email: 'alice@example.com',
  createdAt: '2025-01-01T00:00:00Z',
};

Challenge

Write tests for the validate method of the following Password class. Cover all error cases and the success case.

class Password {
  constructor(value) {
    this.value = value;
  }

  validate() {
    if (this.value.length < 8) {
      throw new Error('Password must be at least 8 characters');
    }
    if (!/[A-Z]/.test(this.value)) {
      throw new Error('Password must contain an uppercase letter');
    }
    if (!/[0-9]/.test(this.value)) {
      throw new Error('Password must contain a number');
    }
    return true;
  }
}

References


Next up: In Day 4, we'll learn about "Mocks, Stubs, and Spies." You'll master jest.fn(), jest.mock(), and jest.spyOn() to isolate dependencies and write focused unit tests!