Skip to content

Step 01: Expect Assertions ​

In this step, we implement the expect() function with basic matchers like toBe() and toEqual().

Goals ​

  • Implement expect(value) that returns an assertion object
  • Add toBe() matcher for strict equality (using Object.is)
  • Add toEqual() matcher for deep equality

Implementation ​

src/expect.ts ​

typescript
import { isEqual } from "ohash";

export function expect<T>(received: T) {
  return {
    toBe(expected: T): void {
      if (!Object.is(received, expected)) {
        throw new Error(`Expected ${String(expected)} but received ${String(received)}`);
      }
    },
    toEqual(expected: T): void {
      if (!isEqual(received, expected)) {
        throw new Error(
          `Expected ${JSON.stringify(expected)} but received ${JSON.stringify(received)}`,
        );
      }
    },
  };
}

src/index.ts ​

typescript
// ... (test, it, runTests from Step 00)

export { expect } from "./expect.js";

Usage Example ​

typescript
import { test, expect, runTests } from "../src/index.js";

test("toBe should compare primitive values", () => {
  expect(1 + 1).toBe(2);
  expect("hello").toBe("hello");
  expect(true).toBe(true);
});

test("toBe should use Object.is for comparison", () => {
  expect(NaN).toBe(NaN);
});

test("toEqual should compare objects by value", () => {
  expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 });
  expect([1, 2, 3]).toEqual([1, 2, 3]);
});

runTests();

Output Example ​

  ✓ toBe should compare primitive values
  ✓ toBe should use Object.is for comparison
  ✓ toEqual should compare objects by value

Tests: 3 passed, 0 failed, 3 total

Exported API ​

APIDescription
expect(value).toBe(x)Strict equality using Object.is
expect(value).toEqual(x)Deep equality using ohash's isEqual

How to Run ​

bash
cd impls/01-expect-assertions
bun run example

Or:

bash
pnpm example:01

Key Concepts ​

Why Object.is instead of ===? ​

Object.is handles edge cases better than ===:

  • Object.is(NaN, NaN) returns true (while NaN === NaN is false)
  • Object.is(0, -0) returns false (while 0 === -0 is true)

This matches the behavior of Vitest and Jest's toBe matcher.

Deep Equality with toEqual ​

For comparing objects and arrays, we use isEqual from ohash. This provides robust deep equality comparison that handles:

  • Nested objects and arrays
  • null and undefined
  • Date, RegExp, Map, Set
  • Circular references

Advanced: Adding More Matchers ​

Now that we have the basic structure, let's try implementing more matchers!

Exercise 1: Implement toBeTruthy() and toBeFalsy() ​

These matchers check if a value is truthy or falsy in JavaScript.

typescript
toBeTruthy(): void {
  if (!received) {
    throw new Error(`Expected ${String(received)} to be truthy`);
  }
},
toBeFalsy(): void {
  if (received) {
    throw new Error(`Expected ${String(received)} to be falsy`);
  }
},

Usage:

typescript
expect(1).toBeTruthy();
expect("hello").toBeTruthy();
expect(0).toBeFalsy();
expect("").toBeFalsy();

Exercise 2: Implement toBeNull(), toBeUndefined(), toBeDefined() ​

typescript
toBeNull(): void {
  if (received !== null) {
    throw new Error(`Expected ${String(received)} to be null`);
  }
},
toBeUndefined(): void {
  if (received !== undefined) {
    throw new Error(`Expected ${String(received)} to be undefined`);
  }
},
toBeDefined(): void {
  if (received === undefined) {
    throw new Error(`Expected value to be defined`);
  }
},

Exercise 3: Implement the .not modifier ​

The .not property returns an object with the same matchers but with inverted logic.

typescript
export function expect<T>(received: T) {
  return {
    toBe(expected: T): void {
      if (!Object.is(received, expected)) {
        throw new Error(`Expected ${String(expected)} but received ${String(received)}`);
      }
    },
    // ... other matchers
    not: {
      toBe(expected: T): void {
        if (Object.is(received, expected)) {
          throw new Error(`Expected value to not be ${String(expected)}`);
        }
      },
      // ... other negated matchers
    },
  };
}

Usage:

typescript
expect(1).not.toBe(2);
expect("hello").not.toBe("world");
expect(undefined).not.toBeNull();

The .not pattern provides readable assertions and is a common feature in testing libraries like Vitest and Jest.