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.