supabase 仓库中的 Vitest 配置实践:vitest.config.ts 核心选项与多包测试体系详解
【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase
Vitest 的配置文件是整个测试体系的地基:它决定测试在什么环境中运行、如何发现测试文件、失败后如何重试、覆盖率如何统计。本文基于 supabase 仓库中的 Vitest 配置参考文档(core-config.md)展开,并结合仓库内apps/studio、apps/docs、apps/www、packages/ui等真实vitest.config.ts文件,讲清楚配置加载优先级、核心选项的取值与语义,以及大型 Monorepo 如何组织多包测试配置。读完后你可以独立完成一个新包的 Vitest 配置,并读懂本仓库各应用包的测试脚本行为。
配置加载机制:vitest.config.ts 与 vite.config.ts
Vitest 从vitest.config.ts或vite.config.ts读取配置,且与 Vite 共享同一套配置格式——测试专属配置全部挂在test属性下,其余字段就是标准 Vite 配置(如plugins、resolve.alias)。这意味着 Vite 的转换管线在测试中同样生效:resolve.alias、插件都能直接复用,这是 Vitest 相对独立测试框架的一个关键设计收益。
配置解析有几条明确的优先级与约定:
vitest.config.ts优先于vite.config.ts;- 可通过
--config命令行参数指定自定义配置路径; - 运行测试时
process.env.VITEST会被设置为true,可用于区分"是否处于测试环境"; - 测试专用选项一律写在
test属性内。
基础配置:vitest.config.ts
最小可用的独立测试配置如下:
// vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { // test options }, })supabase 仓库中packages/ai-commands包就是一个典型的独立配置示例(packages/ai-commands/vitest.config.ts):
/// <reference types="vitest" /> import { defineConfig } from 'vitest/config' export default defineConfig({ test: { environment: 'node', testTimeout: 30000, setupFiles: ['./vitest.setup.ts'], }, })这个 AI 命令包跑的是 Node 侧逻辑,不需要 DOM 环境,因此environment显式设为'node',并把testTimeout从默认的 5000ms 放宽到 30000ms——AI 相关测试通常涉及网络或模型调用,默认超时不够用。
与已有 Vite 配置共存
如果项目已有vite.config.ts,可以直接在其上扩展test属性,同时加上类型引用让 TypeScript 识别该字段:
// vite.config.ts /// <reference types="vitest/config" /> import { defineConfig } from 'vite' export default defineConfig({ test: { globals: true, environment: 'jsdom', }, })注意/// <reference types="vitest/config" />这一行的作用:它把 Vitest 的类型扩展注入到 Vite 的UserConfig中,使test字段获得类型提示与校验,而无需引入额外的类型声明文件。
合并配置:mergeConfig
当 Vite 配置与测试配置分处两个文件、又不想互相 import 副作用时,可以用mergeConfig显式合并:
// vitest.config.ts import { defineConfig, mergeConfig } from 'vitest/config' import viteConfig from './vite.config' export default mergeConfig(viteConfig, defineConfig({ test: { environment: 'jsdom', }, }))mergeConfig是 Vite 提供的深合并工具,会正确合并数组(如plugins追加)与对象(如resolve.alias按 key 合并),避免手写展开运算符导致的字段覆盖。
核心配置选项逐一拆解
参考文档列出了最常用的test选项全集,下面结合仓库中的真实用法逐项说明。
defineConfig({ test: { // 启用全局 API(describe、it、expect),无需 import globals: true, // 测试环境:'node'、'jsdom'、'happy-dom' environment: 'node', // 每个测试文件执行前运行的 setup 文件 setupFiles: ['./tests/setup.ts'], // 测试文件匹配规则 include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'], // 排除规则 exclude: ['**/node_modules/**', '**/dist/**'], // 测试超时(ms) testTimeout: 5000, // 钩子超时(ms) hookTimeout: 10000, // 默认进入 watch 模式 watch: true, // 覆盖率配置 coverage: { provider: 'v8', // 或 'istanbul' reporter: ['text', 'html'], include: ['src/**/*.ts'], }, // 隔离执行(每个文件独立进程) isolate: true, // 执行池:'threads'、'forks'、'vmThreads' pool: 'threads', // 线程/进程数 poolOptions: { threads: { maxThreads: 4, minThreads: 1, }, }, // 测试间自动清空 mock clearMocks: true, // 测试间自动恢复 mock restoreMocks: true, // 失败重试次数 retry: 0, // 首次失败即停止(0 为不启用) bail: 0, }, })globals、environment:环境选择与全局 API
environment: 'node'适合纯逻辑、服务端代码(如 packages/ai-commands/vitest.config.ts);environment: 'jsdom'适合渲染 React 组件的测试。apps/studio与packages/ui均为 UI 应用/组件库,配置里都写了environment: 'jsdom'(见 apps/studio/vitest.config.ts);globals: true免去每个文件import { describe, it, expect } from 'vitest',apps/studio即采用该方式。
值得一提的是,apps/studio的配置中留下了一条 TODO 注释:
environment: 'jsdom', // TODO(kamil): This should be set per test via header in .tsx files only这说明团队有意让环境默认值全局生效,未来计划逐步迁移为在单个.tsx测试文件头部按需声明环境,实现"全局 node、局部 jsdom"的精细控制。
setupFiles 与 globalSetup:两个容易混淆的钩子
setupFiles在每个测试文件的工作进程内执行,适合注入 polyfill、mock、环境变量;而globalSetup在整个测试运行开始时于主进程执行一次,适合准备共享资源(数据库、静态文件等)。
apps/docs同时使用了两者,是理解二者分工的好样本:
- apps/docs/vitest.config.ts 中:
export default defineConfig({ test: { // 排除 examples 目录,避免被误当作测试发现路径 exclude: ['examples/**/*', '**/node_modules/**'], setupFiles: ['vitest.setup.ts'], globalSetup: ['vitest.globalSetup.ts'], }, plugins: [ tsconfigPaths({ root: import.meta.dirname, // 只扫描本应用自己的 tsconfig,避免误扫 examples/** 子目录 projects: ['tsconfig.json'], }), ], })- apps/docs/vitest.setup.ts 在
beforeAll里注入本地 Supabase 连接环境变量(NEXT_PUBLIC_SUPABASE_URL指向http://localhost:54321),并用vi.mock('server-only', ...)屏蔽 Next.js 的 server-only 模块限制,afterAll中还原环境变量并doUnmock; - apps/docs/vitest.globalSetup.ts 则把仓库根目录的
examples/目录整体拷贝到apps/docs/examples/,保证CodeSample.test.ts等测试在npx vitest、pnpm test:local或 IDE 单测等绕过pretest生命周期钩子的场景下也能找到 fixture,否则会报 ENOENT。
对照apps/docs的 package.json 脚本(apps/docs/package.json):
"test": "pnpm supabase start && pnpm run test:local && pnpm supabase stop", "test:local": "vitest --exclude \"**/*.smoke.test.ts\"", "test:local:unwatch": "vitest --exclude \"**/*.smoke.test.ts\" --run",可以看出命令行参数与配置文件是叠加关系:--exclude在配置基础上额外排除 smoke 测试,--run关闭默认 watch 行为以便 CI 一次性跑完。
include / exclude:测试发现与排除
include默认匹配**/*.{test,spec}.*,exclude默认排除node_modules、dist等。仓库中的实际用法展示了两个常见技巧:
- 展开
configDefaults.exclude再追加自定义项,避免默认排除项被覆盖。apps/www与apps/studio都这样写:
// apps/www exclude: [...configDefaults.exclude, '.next/*'],- 按文件粒度排除不稳定的大测试。
apps/studio的exclude里除了.next/*(Next.js 产物目录)外,还单独排除了tests/features/logs/logs-query.test.tsx与tests/features/reports/storage-report.test.tsx两个文件(apps/studio/vitest.config.ts)。
注意apps/docs的配置注释特别强调:exclude只影响测试发现,不影响vite-tsconfig-paths插件对 tsconfig 的扫描,所以插件侧需要单独用projects: ['tsconfig.json']收敛扫描范围。
testTimeout、retry:超时与 CI 抖动策略
testTimeout(单测试超时)与hookTimeout(before/after 类钩子超时)是毫秒数,按需放大,如前文ai-commands包将testTimeout设为 30000;retry是抑制 flaky 测试的标准手段。apps/studio的配置给出了一个值得参考的 CI 分层策略(apps/studio/vitest.config.ts):
const IS_CI = !!process.env.CI test: { // 仅在 CI 中重试不稳定测试;本地失败应立即暴露 retry: IS_CI ? 2 : 0, }本地开发时retry: 0让失败即时可见,CI 环境允许最多 2 次重试来吸收流水线环境的偶发抖动。
coverage:覆盖率
coverage下可指定provider('v8'或'istanbul')、reporter与参与统计的文件范围。仓库各包的差异展示了配置意图:
- apps/studio/vitest.config.ts:
reporter: ['text', 'text-summary', 'lcov'],include: ['lib/**/*.ts']只统计核心逻辑目录,并排除**/*.test.ts(x)自身与个别无需覆盖的工具文件; - packages/ui/vitest.config.ts:
reporter: ['lcov'],include覆盖组件库源码src/**/*.{ts,tsx},配合 package.json 中的test:ci(vitest --run --coverage)与test:report(打开coverage/lcov-report/index.html)形成"CI 收集、本地查看"的完整链路; - packages/ui-patterns/vitest.config.ts 的 reporter 为
['text', 'json', 'html'],说明不同包按消费方式(CI 汇总 vs 人工浏览)选择不同的输出格式。
pool、isolate:执行模型
isolate: true让每个测试文件在独立上下文中执行,避免文件间状态串扰,是大型仓库的安全默认;pool: 'threads'使用 Worker Threads 池执行测试,forks走子进程(隔离性更强但启动成本更高),vmThreads提供 V8 隔离上下文;poolOptions.threads.maxThreads / minThreads控制并发规模,与--no-file-parallelism等 CLI 开关配合可以进一步调节。本仓库各包未显式配置pool,使用默认值即可,从各包测试能稳定运行的现状看,默认参数对这类前端/Node 测试场景是足够的(此结论为从仓库现状推断)。
clearMocks / restoreMocks / bail:Mock 卫生与失败策略
clearMocks: true在每个测试前自动清空 mock 的调用记录与实现,省去手写vi.clearAllMocks();restoreMocks: true更进一步,把被vi.spyOn替换的原函数还原,防止跨测试泄漏;bail: 0表示失败后继续跑完剩余测试(设为n则累计失败 n 次后停止),本地调试想"跑一个错一个"时可临时调成 1。
条件化配置:mode 与 process.env.VITEST
配置函数可以接收mode参数做条件化分支,例如测试模式下跳过某些插件:
export default defineConfig(({ mode }) => ({ plugins: mode === 'test' ? [] : [myPlugin()], test: { // test options }, }))同时process.env.VITEST === 'true'是另一条可靠的判断路径,适合在共享代码(而非配置文件)中切换行为。apps/studio的IS_CI条件化(见上文 retry 示例)则是同一思路在 CI 维度的延伸:条件分支不必局限于测试/非测试,任何环境变量都可驱动配置。
Monorepo 场景:projects 与多包配置两种组织方式
参考文档给出的projects方案是在一个 Vitest 进程内运行多套配置:
defineConfig({ test: { projects: [ 'packages/*', { test: { name: 'unit', include: ['tests/unit/**/*.test.ts'], environment: 'node', }, }, { test: { name: 'integration', include: ['tests/integration/**/*.test.ts'], environment: 'jsdom', }, }, ], }, })projects数组成员既可以是目录通配(如'packages/*',每个子目录用自己的配置),也可以是内联配置对象,适合"同一个测试树按 unit/integration 分泳道"的场景。
而 supabase 仓库本身采用的是另一种 Monorepo 组织方式:pnpm workspace + Turbo,每个应用/包各自维护独立的vitest.config.ts(apps/docs、apps/studio、apps/www、packages/ui、packages/ui-patterns、packages/ai-commands、packages/dev-tools等),由根目录 package.json 的脚本按 filter 分发执行:
"test:docs": "turbo run test --filter=docs", "test:ui": "turbo run test --filter=ui", "test:ui-patterns": "turbo run test --filter=ui-patterns", "test:studio": "turbo run test --filter=studio"从源码结构看,这种"每包独立配置 + Turbo 编排"的方式让各包可以完全自主地选择 environment、setup 与覆盖率范围(前文各包的差异即为例证),代价是需要在每个包内重复少量通用配置;两种方案各有取舍,可依据包之间配置的同质程度选择。
关键要点回顾
- Vitest 复用 Vite 的转换管线:
resolve.alias、plugins在测试中直接生效,本仓库各配置普遍依赖vite-tsconfig-paths与@vitejs/plugin-react插件; vitest.config.ts优先于vite.config.ts;自定义路径用--config指定;process.env.VITEST在测试运行时为'true';- 测试专属选项全部收敛在
test属性下,其余字段是标准 Vite 配置; - 仓库实战经验:
setupFiles(每文件)与globalSetup(全局一次)分工明确;exclude记得展开configDefaults.exclude;retry按 CI/本地分层设置;coverage.include只圈定真正想统计的源码目录。
掌握以上内容后,你既能按参考文档快速写出新包的vitest.config.ts,也能对照 apps/studio/vitest.config.ts、apps/docs/vitest.config.ts 等真实配置理解每个选项在本仓库测试体系中的实际落点。
【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考