Skip to main content

vitest

Vitest 4.x 測試框架最佳實踐指南。當需要設定測試、撰寫單元/元件測試、mocking、coverage、整合 Astro/Three.js 測試時使用。

跳到安装

来源信息

仓库
recca0120/codeatlas
最近来源活动
2026年3月27日 20:32
检测到的 SKILL.md 语言
中文
星标
1
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
vitest
description
Vitest 4.x 測試框架最佳實踐指南。當需要設定測試、撰寫單元/元件測試、mocking、coverage、整合 Astro/Three.js 測試時使用。
# Vitest Best Practices Guide (4.x) ## 版本資訊 - 最新穩定版:**Vitest 4.1.2**(2026-03) - Vitest 4.0 新增:Browser Mode 穩定化、`toMatchScreenshot()`、`expect.schemaMatching`、`expect.assert` ## 安裝 ```bash npm install -D vitest @vitest/coverage-v8 ``` ## 設定(vitest.config.ts) ```ts import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'node', globals: true, include: ['src/**/*.{test,spec}.{ts,tsx}'], exclude: ['node_modules', 'dist', 'e2e'], restoreMocks: true, // 自動還原 mock pool: 'forks', // 預設,最安全 coverage: { provider: 'v8', include: ['src/**/*.ts'], exclude: ['src/**/*.d.ts', 'src/**/*.test.ts'], thresholds: { statements: 80, branches: 80, functions: 80, lines: 80 }, }, }, }); ``` ## Astro 整合 ```ts import { getViteConfig } from 'astro/config'; export default getViteConfig({ test: { /* vitest options */ }, }); ``` ### 測試 Astro 元件(Container API,Astro 4.9+) ```ts import { experimental_AstroContainer as AstroContainer } from 'astro/container'; import Card from '../src/components/Card.astro'; test('Card renders', async () => { const container = await AstroContainer.create(); const html = await container.renderToString(Card, { slots: { default: 'content' }, }); expect(html).toContain('content'); }); ``` ## Mocking ### vi.fn() — 獨立 mock function ```ts const handler = vi.fn(); handler('arg'); expect(handler).toHaveBeenCalledWith('arg'); ``` ### vi.spyOn() — 觀察既有方法 ```ts const spy = vi.spyOn(console, 'log'); doSomething(); expect(spy).toHaveBeenCalledWith('expected'); ``` ### vi.mock() — 替換整個模組(會被 hoisted) ```ts vi.mock('./api', () => ({ fetchData: vi.fn().mockResolvedValue({ id: 1 }), })); ``` ### 清理層級 | 方法 | 效果 | |------|------| | `mockClear()` | 清除呼叫記錄 | | `mockReset()` | 清除 + 移除實作 | | `mockRestore()` | 還原原始(僅 spy) | **建議**:設定 `restoreMocks: true` 自動還原。 ## 測試 Async / Timers / Fetch ### Async ```ts it('fetches data', async () => { const data = await fetchUser(1); expect(data.name).toBe('Alice'); }); ``` ### Fake Timers ```ts it('debounce', () => { vi.useFakeTimers(); const fn = vi.fn(); debounce(fn, 300)(); vi.advanceTimersByTime(300); expect(fn).toHaveBeenCalledOnce(); vi.useRealTimers(); }); ``` ### Mock fetch ```ts vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ data: 'test' }), })); ``` ## 測試 Three.js / WebGL 程式 WebGL 在 Node.js 不可用,策略: ### Mock Renderer ```ts vi.mock('three', async (importOriginal) => { const actual = await importOriginal<typeof import('three')>(); return { ...actual, WebGLRenderer: vi.fn().mockImplementation(() => ({ setSize: vi.fn(), render: vi.fn(), domElement: document.createElement('canvas'), dispose: vi.fn(), setPixelRatio: vi.fn(), })), }; }); ``` **原則**:將場景邏輯(物件位置、層級、材質)與渲染分開測試。Mock `WebGLRenderer`,專注測試 scene graph。 ## 測試 Node.js 腳本(如 data collection) ```ts vi.mock('node:fs/promises', () => ({ readFile: vi.fn().mockResolvedValue('content'), writeFile: vi.fn().mockResolvedValue(undefined), })); vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve([{ id: 1 }]), })); import { collectData } from './collect'; it('collects data', async () => { const result = await collectData(); expect(result).toHaveLength(1); }); ``` ## Workspace / Projects ```ts export default defineConfig({ test: { projects: [ { extends: true, test: { name: 'unit', include: ['src/**/*.unit.test.ts'], environment: 'node' }, }, { extends: true, test: { name: 'components', include: ['src/**/*.component.test.ts'], environment: 'jsdom' }, }, ], }, }); ``` 執行特定 project:`vitest --project unit` ## Coverage:v8 vs Istanbul | | v8(預設) | Istanbul | |---|---|---| | 速度 | 較快 | 較慢 | | 安裝 | `@vitest/coverage-v8` | `@vitest/coverage-istanbul` | | 適用 | V8 runtime(Node.js) | 所有 JS runtime | **建議用 v8**。忽略特定行:`/* v8 ignore next */` ## DOM 測試環境 - **happy-dom**:較快,大多數情況適用 - **jsdom**:DOM 實作更完整 ```ts // 單檔覆蓋 // @vitest-environment happy-dom ``` ## Snapshot Testing ```ts expect(result).toMatchSnapshot(); // 檔案 snapshot expect(result).toMatchInlineSnapshot(); // inline(自動填入) expect(html).toMatchFileSnapshot('./expected.html'); // 自訂檔案 ``` 更新:`vitest -u` ## In-Source Testing ```ts export function add(a: number, b: number) { return a + b; } if (import.meta.vitest) { const { it, expect } = import.meta.vitest; it('adds', () => expect(add(1, 2)).toBe(3)); } ``` 設定:`includeSource: ['src/**/*.ts']` Production 移除:`define: { 'import.meta.vitest': 'undefined' }` ## 效能優化 | 策略 | 做法 | |------|------| | Pool | `threads`(大專案)、`forks`(預設,安全) | | 停用隔離 | `isolate: false`(無副作用時) | | 限縮搜尋 | `dir: 'src'` | | CI 分片 | `--shard=1/4` + `--merge-reports` | ## 常見陷阱 1. **vi.mock() 被 hoisted** — 不能用下方宣告的變數 2. **fetch + threads pool** — 會 "Failed to terminate worker",改用 `forks` 3. **Native modules + threads** — segfault,改用 `forks` 4. **path alias 找不到** — 安裝 `vite-tsconfig-paths` 5. **忘記 await async** — 用 `expect().rejects` 處理預期錯誤 ## Type Testing 檔名:`*.test-d.ts`,執行:`vitest --typecheck` ```ts import { expectTypeOf } from 'vitest'; expectTypeOf(add).parameter(0).toBeNumber(); expectTypeOf(add(1, 2)).toEqualTypeOf<number>(); ```
在 GitHub 查看