一键导入
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(最後選擇)