モック
テスト時に外部依存を模倣するオブジェクトで、テストの独立性と速度を確保する
テスト設計
モックとは
モック (Mock) は、テスト時に外部依存 (DB、API、ファイルシステム) を模倣するオブジェクトで、テストの独立性と速度を確保する。実際の DB に接続せずにテストを実行でき、ネットワーク障害などの異常系もシミュレートできる。
テストダブルの種類
| 種類 | 説明 | 例 |
|---|---|---|
| Mock | 呼び出しを記録し、期待通りに呼ばれたか検証 | vi.fn() |
| Stub | 固定値を返す | vi.fn().mockReturnValue(42) |
| Spy | 実際の関数を呼びつつ、呼び出しを記録 | vi.spyOn(obj, 'method') |
| Fake | 簡易的な実装 (InMemory DB) | InMemoryUserRepository |
Vitest でのモック
import { vi, describe, it, expect } from 'vitest';
// 関数のモック
const getUser = vi.fn().mockResolvedValue({ id: '1', name: 'Alice' });
// モジュールのモック
vi.mock('./db', () => ({
getUser: vi.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
}));
describe('OrderService', () => {
it('ユーザーが存在する場合に注文を作成する', async () => {
const result = await createOrder('1', [{ productId: 'P1', qty: 2 }]);
expect(result.status).toBe('created');
expect(getUser).toHaveBeenCalledWith('1');
});
});
モックしすぎの問題
// ❌ モックしすぎ: テストが実装の詳細に依存
it('should call db.put with correct params', () => {
createUser({ name: 'Alice' });
expect(db.put).toHaveBeenCalledWith({
TableName: 'users',
Item: { id: expect.any(String), name: 'Alice' },
});
});
// → db.put の引数が変わるとテストが壊れる (実装の詳細)
// ✅ 振る舞いをテスト
it('should create a user and return it', async () => {
const user = await createUser({ name: 'Alice' });
expect(user.name).toBe('Alice');
expect(user.id).toBeDefined();
});
Fake (インメモリ実装)
class InMemoryUserRepository implements UserRepository {
private store = new Map<string, User>();
async findById(id: string) { return this.store.get(id) ?? null; }
async save(user: User) { this.store.set(user.id, user); }
}
// テストで使用
const repo = new InMemoryUserRepository();
const service = new UserService(repo);
Fake はモックより実装に近く、テストの信頼性が高い。
モックの判断基準
| ケース | 推奨 |
|---|---|
| 外部 API (HTTP) | ✅ モック |
| DB (DynamoDB) | Fake (InMemory) or モック |
| 純粋関数 | ❌ モック不要 |
時刻 (Date.now()) |
✅ モック (vi.useFakeTimers()) |
モックを扱う関連書籍も多い。