Step 01: Expect Assertions
このステップでは、expect() 関数と基本的なマッチャー toBe(), toEqual() を実装します。
目標
expect(value)でアサーションオブジェクトを返すtoBe()マッチャーで厳密等価比較(Object.isを使用)toEqual()マッチャーで深い等価比較
実装
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
// ... (Step 00 の test, it, runTests)
export { expect } from "./expect.js";使用例
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();出力例
✓ 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エクスポート API
| API | 説明 |
|---|---|
expect(value).toBe(x) | Object.is による厳密等価比較 |
expect(value).toEqual(x) | ohash の isEqual による深い等価比較 |
実行方法
bash
cd impls/01-expect-assertions
bun run exampleまたは:
bash
pnpm example:01ポイント解説
なぜ === ではなく Object.is を使うのか?
Object.is は === よりもエッジケースを正しく処理します:
Object.is(NaN, NaN)はtrueを返す(NaN === NaNはfalse)Object.is(0, -0)はfalseを返す(0 === -0はtrue)
これは Vitest や Jest の toBe マッチャーの動作と一致しています。
toEqual での深い等価比較
オブジェクトや配列の比較には ohash の isEqual を使用しています。これにより以下を正しく比較できます:
- ネストしたオブジェクトと配列
nullとundefinedDate,RegExp,Map,Set- 循環参照
発展: より多くのマッチャーを追加する
基本構造ができたので、より多くのマッチャーを実装してみましょう!
演習 1: toBeTruthy() と toBeFalsy() を実装する
これらのマッチャーは、値が truthy か falsy かをチェックします。
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`);
}
},使用例:
typescript
expect(1).toBeTruthy();
expect("hello").toBeTruthy();
expect(0).toBeFalsy();
expect("").toBeFalsy();演習 2: 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`);
}
},演習 3: .not 修飾子を実装する
.not プロパティは、同じマッチャーを持つが論理が反転したオブジェクトを返します。
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)}`);
}
},
// ... 他のマッチャー
not: {
toBe(expected: T): void {
if (Object.is(received, expected)) {
throw new Error(`Expected value to not be ${String(expected)}`);
}
},
// ... 他の否定マッチャー
},
};
}使用例:
typescript
expect(1).not.toBe(2);
expect("hello").not.toBe("world");
expect(undefined).not.toBeNull();.not パターンは可読性の高いアサーションを提供し、Vitest や Jest で一般的な機能です。