# 機能設計書 50-モック機能

## 概要

本ドキュメントは、Bunテストランナーのモック機能に関する設計書である。jest.fn()、jest.spyOn()、jest.mock()、タイマーモック等の機能について詳述する。

### 本機能の処理概要

モック機能は、テスト実行時に関数やモジュール、タイマー等を模倣（モック）して、テスト対象コードを分離テストするための機能である。Jest互換のモックAPIを提供する。

**業務上の目的・背景**：ユニットテストでは、テスト対象のコードを外部依存から分離してテストすることが重要である。モック機能により、APIコール、データベース接続、ファイルシステム操作等を模倣し、高速かつ再現性のあるテストを実現できる。副作用のない純粋なテストにより、バグの特定と修正が容易になる。

**機能の利用シーン**：
- `jest.fn()` でモック関数を作成
- `jest.spyOn()` で既存メソッドをスパイ
- `jest.mock()` でモジュール全体をモック
- `jest.useFakeTimers()` でタイマーをモック
- `jest.setSystemTime()` でシステム時刻をモック

**主要な処理内容**：
1. モック関数の作成と呼び出し記録
2. 既存関数のスパイと置換
3. モジュールの動的モック
4. タイマー（setTimeout等）のモック
5. Date.now()のモック
6. モック状態のリセット/リストア

**関連システム・外部連携**：
- JavaScriptCore VM
- モジュールローダー
- 組み込みタイマー

**権限による制御**：特になし

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | test | 主機能 | モックを使用したテスト実行 |

## 機能種別

テスト機能（モック・スタブ）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| implementation | function | No | モック関数の実装 | 関数 |
| object | object | Yes（spyOn） | スパイ対象オブジェクト | オブジェクト |
| method | string | Yes（spyOn） | スパイ対象メソッド名 | 文字列 |
| module_path | string | Yes（mock） | モックモジュールパス | 有効なモジュールパス |
| time | number/Date | No | モック時刻 | 数値またはDateオブジェクト |

### 入力データソース

- テストコード内の関数呼び出し
- モジュールimport

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| mock.calls | array | 呼び出し引数の配列 |
| mock.results | array | 戻り値/例外の配列 |
| mock.instances | array | コンストラクタ呼び出し時のthis |
| mock.contexts | array | 呼び出しコンテキスト |
| mock.lastCall | array | 最後の呼び出し引数 |

### 出力先

- モックオブジェクトのプロパティ
- テスト結果（アサーション）

## 処理フロー

### 処理シーケンス

```
1. モック作成
   └─ jest.fn()またはjest.spyOn()でモック生成
2. モック設定
   └─ mockImplementation、mockReturnValue等で振る舞い設定
3. テスト実行
   └─ テスト対象コードがモックを呼び出し
4. 呼び出し記録
   └─ 引数、戻り値、例外を記録
5. アサーション
   └─ toHaveBeenCalled等でモック呼び出しを検証
6. リセット/リストア
   └─ afterEachでモック状態をクリア
```

### フローチャート

```mermaid
flowchart TD
    A[テスト開始] --> B{モックタイプ?}
    B -->|jest.fn| C[新規モック関数作成]
    B -->|jest.spyOn| D[既存メソッドをラップ]
    B -->|jest.mock| E[モジュールモック登録]
    B -->|useFakeTimers| F[タイマーモック有効化]
    C --> G[mockプロパティ初期化]
    D --> G
    G --> H[振る舞い設定]
    H --> I[テスト実行]
    I --> J[呼び出し記録]
    J --> K{複数呼び出し?}
    K -->|Yes| J
    K -->|No| L[アサーション]
    L --> M{リストア必要?}
    M -->|Yes| N[mockRestore]
    M -->|No| O[テスト終了]
    N --> O
    E --> I
    F --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-01 | 呼び出し記録 | すべてのモック呼び出しを記録 | モック使用時 |
| BR-50-02 | リストア | mockRestoreで元の実装に戻す | spyOn使用時 |
| BR-50-03 | クリア | mockClearで呼び出し記録のみクリア | テスト間 |
| BR-50-04 | リセット | mockResetで実装もリセット | テスト間 |
| BR-50-05 | restoreAllMocks | すべてのモックをリストア | afterAll時 |
| BR-50-06 | タイマー進行 | jest.advanceTimersByTime()で時間を進める | fakeTimers使用時 |

### 計算ロジック

呼び出しカウント:
```
calls.length = モック関数が呼び出された回数
```

## データベース操作仕様

本機能はデータベースを使用しない。

### 操作別データベース影響一覧

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| InvalidObject | オブジェクトエラー | spyOnの対象がオブジェクトでない | オブジェクトを渡す |
| InvalidMethod | メソッドエラー | 指定メソッドが存在しない | メソッド名を確認 |
| ModuleNotFound | モジュールエラー | モックモジュールが見つからない | パスを確認 |
| NotMockable | モック不可 | 対象がモック可能でない | 別の方法を検討 |
| AlreadyMocked | 二重モック | 既にモック済み | リストア後に再モック |

### リトライ仕様

モック操作は自動リトライしない。

## トランザクション仕様

モック状態はテストファイル単位で管理。ファイル終了時にリストア推奨。

## パフォーマンス要件

- モック関数呼び出し: 1ms以下のオーバーヘッド
- 呼び出し記録: O(1)
- spyOn設定: 10ms以下

## セキュリティ考慮事項

- モックはテスト実行時のみ有効
- 本番コードには影響しない

## 備考

- Jest モックAPI互換
- Vitest（vi）互換APIも提供
- mockImplementationOnce で呼び出しごとの振る舞い設定可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: モック登録を理解する

Jest互換モック機能がどのように登録されるかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | jest.zig | `src/bun.js/test/jest.zig` | createMockObjects関数 |

**読解のコツ**: createMockObjects関数がjest/viオブジェクトを構築し、各モック関数を登録する。

#### Step 2: 個別モック関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | jest.zig | `src/bun.js/test/jest.zig` | JSMock__関数群 |

**主要処理フロー**:
- **214行目**: setSystemTimeでシステム時刻モック
- **217行目**: mockFnでjest.fn()を実装
- **218行目**: spyOnでjest.spyOn()を実装
- **219行目**: restoreAllMocksですべてのモックをリストア
- **220行目**: clearAllMocksですべてのモックをクリア
- **221行目**: mockModuleFnでjest.mock()を実装

#### Step 3: jestオブジェクト構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | jest.zig | `src/bun.js/test/jest.zig` | jestオブジェクト構築 |

**主要処理フロー**:
- **227-238行目**: jestオブジェクトの構築
  - jest.fn()
  - jest.mock()
  - jest.spyOn()
  - jest.restoreAllMocks()
  - jest.clearAllMocks()
  - jest.resetAllMocks()
  - jest.setSystemTime()
  - jest.now()
  - jest.setTimeout()

#### Step 4: viオブジェクト（Vitest互換）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | jest.zig | `src/bun.js/test/jest.zig` | viオブジェクト構築 |

**主要処理フロー**:
- **242-249行目**: viオブジェクトの構築（Vitest互換）
  - vi.fn()
  - vi.mock()
  - vi.spyOn()
  - vi.restoreAllMocks()
  - vi.resetAllMocks()
  - vi.clearAllMocks()

#### Step 5: FakeTimersを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | bun_test.zig | `src/bun.js/test/bun_test.zig` | FakeTimers |

**主要処理フロー**:
- **251行目（jest.zig）**: FakeTimers.putTimersFnsでタイマーモック関数を登録

### プログラム呼び出し階層図

```
createMockObjects()
    │
    ├─ setSystemTime登録
    │      └─ JSMock__jsSetSystemTime
    │
    ├─ mockFn登録（jest.fn）
    │      └─ JSMock__jsMockFn
    │
    ├─ spyOn登録
    │      └─ JSMock__jsSpyOn
    │
    ├─ restoreAllMocks登録
    │      └─ JSMock__jsRestoreAllMocks
    │
    ├─ clearAllMocks登録
    │      └─ JSMock__jsClearAllMocks
    │
    ├─ mockModuleFn登録（jest.mock）
    │      └─ JSMock__jsModuleMock
    │
    ├─ jestオブジェクト構築
    │      └─ 上記関数をプロパティとして設定
    │
    ├─ viオブジェクト構築
    │      └─ 同様の関数をVitest互換で設定
    │
    └─ FakeTimers.putTimersFns()
           └─ タイマーモック関数を登録
```

### データフロー図

```
[入力]                    [処理]                         [出力]

jest.fn() ───────────────▶ モック関数生成 ─────────────▶ MockFunction
                              │
                              ▼
関数呼び出し ─────────────▶ 呼び出し記録 ─────────────▶ mock.calls[]
                              │
                              ▼
                      戻り値返却 ─────────────────▶ mock.results[]
                              │
jest.spyOn(obj, 'method')     │
    │                         ▼
    └─────────────────▶ 既存関数をラップ ─────────▶ SpyInstance
                              │
jest.mock('module')           │
    │                         ▼
    └─────────────────▶ モジュールモック登録 ────▶ モック済みモジュール
                              │
jest.useFakeTimers()          │
    │                         ▼
    └─────────────────▶ タイマーモック有効化 ───▶ FakeTimers
                              │
jest.setSystemTime(date)      │
    │                         ▼
    └─────────────────▶ システム時刻設定 ───────▶ Date.now()モック
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| jest.zig | `src/bun.js/test/jest.zig` | ソース | モック関数登録の中核 |
| bun_test.zig | `src/bun.js/test/bun_test.zig` | ソース | FakeTimers統合 |
| bindings.cpp | `src/bun.js/bindings/bindings.cpp` | ソース | JSMock__関数実装 |
| expect.zig | `src/bun.js/test/expect.zig` | ソース | モック関連マッチャー |
| ModuleLoader.zig | `src/bun.js/ModuleLoader.zig` | ソース | モジュールモック対応 |
| fake_timers.zig | `src/bun.js/test/fake_timers.zig` | ソース | タイマーモック実装 |
