بنقرة واحدة
test-doubles
Test Doubles 最佳實踐指南(Vitest + TypeScript)。當需要選擇 Mock/Stub/Fake/Spy/Dummy、或討論測試隔離策略時使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Test Doubles 最佳實踐指南(Vitest + TypeScript)。當需要選擇 Mock/Stub/Fake/Spy/Dummy、或討論測試隔離策略時使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
cubejs (Rubik's Cube solver, Kociemba two-phase) 使用指南。當需要實作魔術方塊自動解法、狀態字串序列化、或 scramble 產生時使用。
msw-fetch-mock 套件使用指南(搭配 MSW v2 + Vitest)。當需要 mock HTTP 請求、測試 fetch 呼叫、驗證 API 互動時使用。
React 19 最佳實踐指南。當需要撰寫 React 元件、使用 Hooks、處理狀態管理、或搭配 TypeScript/Vite 開發時使用。
React Three Fiber (R3F) + Three.js + drei 最佳實踐。當需要建立 3D 場景、處理相機/光照、互動事件、動畫、效能優化時使用。
TDD (Test-Driven Development) 最佳實踐指南,搭配 Vitest + React Testing Library。當需要撰寫測試、實踐 Red-Green-Refactor、設計測試結構時使用。應在開發新功能時主動套用。
Vite 6/7 最佳實踐指南。當需要初始化 Vite 專案、設定 plugin、配置 build、除錯 HMR、path alias 或整合 React/TS/Vitest 時使用。
| name | test-doubles |
| description | Test Doubles 最佳實踐指南(Vitest + TypeScript)。當需要選擇 Mock/Stub/Fake/Spy/Dummy、或討論測試隔離策略時使用。 |
| 類型 | 目的 | 有行為? | 會驗證? |
|---|---|---|---|
| Dummy | 佔位,不會被用 | ❌ | ❌ |
| Stub | 回傳固定值 | ✅ 固定 | ❌ |
| Fake | 簡化但可運作 | ✅ 真實 | ❌ |
| Spy | 記錄呼叫 | ✅ 原行為 | ✅ 事後 |
| Mock | 預設行為 + 事先預期 | ✅ | ✅ 事先 |
Fake > Stub > Spy > Mock > Dummy
原因:
const spy = vi.spyOn(console, 'log')
myFunction()
expect(spy).toHaveBeenCalledWith('hello')
spy.mockRestore()
const onSave = vi.fn() // Spy/Mock
const fetchUser = vi.fn().mockResolvedValue({ id: 1 }) // Stub
expect(onSave).toHaveBeenCalledTimes(1)
expect(onSave).toHaveBeenCalledWith({ name: 'x' })
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
}))
import { fetchUser } from './api'
// fetchUser 是 mock
vi.mock 被提升到檔頂,所以不能直接引用外部變數。要共享 mock instance:
const { mockFetch } = vi.hoisted(() => ({ mockFetch: vi.fn() }))
vi.mock('./api', () => ({ fetchUser: mockFetch }))
beforeEach(() => mockFetch.mockReset())
class FakeCubeStorage implements CubeStorage {
private data = new Map<string, string>()
save(id: string, state: string) { this.data.set(id, state) }
load(id: string) { return this.data.get(id) ?? null }
}
// 測試中注入
const store = new CubeService(new FakeCubeStorage())
Fake 優於 Mock 的時機:被依賴的介面多次呼叫、行為有狀態累積(DB、cache、queue)。
import { afterEach, vi } from 'vitest'
afterEach(() => {
vi.clearAllMocks() // 清 call history,保留 implementation
// 或 vi.resetAllMocks() // 連 implementation 也清掉
// 或 vi.restoreAllMocks() // 復原 spyOn 的原始實作
})
或 vite.config.ts 設 test.clearMocks: true 自動做。
// 取得 mocked 函式型別
import { fetchUser } from './api'
const mockedFetch = vi.mocked(fetchUser)
mockedFetch.mockResolvedValue({ id: 1 })
// 部分 mock
vi.mock('./api', async (importOriginal) => {
const actual = await importOriginal<typeof import('./api')>()
return { ...actual, fetchUser: vi.fn() }
})
expect(mock).toHaveBeenCalled() 卻沒看結果 → 測試耦合實作。應測最終行為。afterEach 清。vi.mock 提升到檔頂,vi.doMock 則不提升(動態 mock)。mockRestore() 或 restoreMocks: true。vi.useFakeTimers() / vi.setSystemTime(),記得 vi.useRealTimers() 還原。需要隔離嗎?
├─ 不需要 → 直接用真實依賴
└─ 需要(慢、外部、不確定性)
├─ 有狀態、呼叫多 → Fake
├─ 單次回固定值 → Stub (vi.fn().mockReturnValue)
├─ 驗證被呼叫 → Spy (vi.spyOn)
└─ 複雜預期互動 → Mock(最後選擇)