원클릭으로
test-doubles
Test Doubles 最佳實踐指南,包含五種類型詳解與使用優先順序。當需要選擇 Mock/Stub/Fake/Spy/Dummy、或討論測試隔離策略時使用。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Test Doubles 最佳實踐指南,包含五種類型詳解與使用優先順序。當需要選擇 Mock/Stub/Fake/Spy/Dummy、或討論測試隔離策略時使用。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Zod v4 schema validation 最佳實踐指南。當需要定義 schema、驗證/解析 JSON 資料、type inference、或處理 unknown data 時使用。
Svelte 5 + Astro 整合最佳實踐指南。當需要建立 Svelte 元件、使用 runes API、整合 Astro islands、或用 Testing Library 測試 Svelte 元件時使用。
GitHub GraphQL API 最佳實踐指南。當需要使用 GraphQL 查詢使用者資料、處理 cursor pagination、計算 rate limit、或除錯 GraphQL errors 時使用。
gayanvoice/top-github-users 架構參考指南。當需要了解 GitHub 使用者排行榜的資料抓取管線、國家設定、排行計算邏輯、已知問題、或社群需求時使用。
Commander.js v14 CLI 框架最佳實踐。當需要建立 CLI 工具、解析命令列參數、設計 subcommands 時使用。
GitHub Actions CI/CD 最佳實踐指南。當需要設定 workflow、cron 排程、GitHub Pages 部署、使用 Octokit API、或處理 rate limiting 時使用。
| name | test-doubles |
| description | Test Doubles 最佳實踐指南,包含五種類型詳解與使用優先順序。當需要選擇 Mock/Stub/Fake/Spy/Dummy、或討論測試隔離策略時使用。 |
| 類型 | 定義 | 用途 |
|---|---|---|
| Dummy | 傳遞但從未使用的物件,填充參數列表 | 滿足型別要求 |
| Stub | 提供預定義(canned)回應 | 狀態驗證:檢查 SUT 的結果 |
| Spy | Stub + 記錄呼叫資訊 | 事後驗證互動 |
| Mock | 預設期望的呼叫,自身驗證失敗 | 行為驗證(最嚴格) |
| Fake | 完整但簡化的實作(不適合 production) | 有真實邏輯、維護狀態 |
1. Real Implementation ← 最優先:快速、確定性、簡單依賴
2. Fake ← 次佳:忠實行為、維護狀態
3. Stub ← 回傳固定值:用於狀態驗證
4. Spy ← Stub + 呼叫記錄:需要驗證呼叫時
5. Mock ← 最後手段:嚴格行為驗證、最多耦合
能用真實物件嗎?
├─ YES → 用真實實作
└─ NO → 依賴是否複雜且有狀態?
├─ YES → 寫 Fake
└─ NO → 需要驗證互動嗎?
├─ NO → 用 Stub(回傳固定資料)
└─ YES → 需要事前期望還是事後檢查?
├─ 事後 → 用 Spy
└─ 事前 → 用 Mock(最後手段)
// DUMMY — 填充參數
const dummyLogger: Logger = { log: () => {}, error: () => {} };
// STUB — vi.fn() 回傳固定值
const getUser = vi.fn().mockReturnValue({ id: 1, name: 'Alice' });
// SPY — 觀察真實行為
const spy = vi.spyOn(userService, 'save');
// ... 執行程式碼 ...
expect(spy).toHaveBeenCalledWith({ id: 1, name: 'Alice' });
// MOCK — 替換整個模組(vi.mock 會被 hoisted)
vi.mock('./emailService', () => ({
sendEmail: vi.fn().mockResolvedValue(true),
}));
// FAKE — 真實運作的簡化實作
class FakeUserRepository implements UserRepository {
private users = new Map<string, User>();
async findById(id: string) { return this.users.get(id) ?? null; }
async save(user: User) { this.users.set(user.id, user); }
}
| vi.spyOn | vi.mock | |
|---|---|---|
| 範圍 | 區域性,單一方法 | 全檔案,整個模組 |
| 型別安全 | 是 | 較弱 |
| 保留原始 | 預設保留 | 全部替換 |
| Hoisting | 不會 | 會被提升到檔案頂部 |
vi.mock 是 footgun — hoisting 行為和全檔案範圍讓測試難以推理。預設用 vi.spyOn。
Google 經驗:大量 mock 的獨立測試「需要持續維護但很少發現 bug」。
一個測試需要 5+ mock → 設計有太多依賴,重構 production code。
// 壞 — 耦合實作
expect(mockRepo.save).toHaveBeenCalledTimes(1);
// 好 — 驗證狀態/結果
const saved = await fakeRepo.findByName('Alice');
expect(saved).not.toBeNull();
每次重構都壞 → 測試耦合了內部細節而非行為。
不要直接 mock 第三方 API,而是:
// 壞 — 直接 mock fetch
vi.mock('node-fetch');
// 好 — 擁有自己的介面
interface HttpClient {
get<T>(url: string): Promise<T>;
}
class FakeHttpClient implements HttpClient {
private responses = new Map<string, unknown>();
stubGet(url: string, data: unknown) { this.responses.set(url, data); }
async get<T>(url: string): Promise<T> {
return this.responses.get(url) as T;
}
}
DI 是可測試性的最重要模式。不需要框架 — constructor injection + interface 就夠了:
// 1. 定義介面
interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
}
// 2. Production 實作
class PostgresUserRepository implements UserRepository { /* SQL */ }
// 3. Fake
class FakeUserRepository 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); }
}
// 4. Service 接受介面
class UserService {
constructor(private repo: UserRepository) {}
}
// 5. 測試 — 注入 fake
it('creates a user', async () => {
const repo = new FakeUserRepository();
const service = new UserService(repo);
const user = await service.createUser({ name: 'Alice' });
expect(await repo.findById(user.id)).toEqual(user);
});
// MSW
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
const server = setupServer(
http.get('https://api.github.com/users/:id', ({ params }) => {
return HttpResponse.json({ id: params.id, name: 'Alice' });
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
| 面向 | Fake | Mock |
|---|---|---|
| 忠實度 | 高 — 真實行為 | 低 — 固定回應 |
| 維護者 | 依賴的擁有者 | 每個測試作者 |
| 重構安全 | 耐重構 | 重構就壞 |
| 可讀性 | 像真實程式碼 | 像設定檔 |
| 初始成本 | 較高 | 較低 |
| 長期成本 | 較低(1 fake, N tests) | 較高(N mocks) |
Google 指導:對複雜依賴,Fake 優於 Mock。
驗證 test doubles 忠實反映真實外部服務:
| 元件 | 策略 |
|---|---|
| Country Config loader | Real — 純邏輯,快速 |
| GitHub API client | Fake HttpClient + MSW 整合測試 |
| Rate limiter | Fake timers(vi.useFakeTimers) |
| 排序/去重邏輯 | Real — 純函式 |
| Three.js renderer | Mock WebGLRenderer |
| OG image 產生 | Stub satori 回傳固定 SVG |
| File I/O | Fake FS 或 vi.mock('node:fs/promises') |