Vitest describe API 深度解析:从测试分组到参数化与并发套件 —— 以 Supabase 单仓实践为例

Vitest describe API 深度解析:从测试分组到参数化与并发套件 —— 以 Supabase 单仓实践为例 Vitest describe API 深度解析从测试分组到参数化与并发套件 —— 以 Supabase 单仓实践为例【免费下载链接】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 的describe套件API 展开如何用它把相关测试组织成逻辑清晰的套件树、如何借助 options 与修饰符skip/only/concurrent/shuffle 等控制套件的执行行为、以及describe.each/describe.for参数化套件的写法。读完后你将掌握 Vitest 套件组织的完整 API 面并能在 Supabase 这类 pnpm monorepo 中直接定位真实用例如 Studio 前端的参数化测试把分组、共享 setup 与条件跳过落地到日常测试代码中。describe 的定位测试分组的入口Supabase 仓库在 .agents/skills/vitest/SKILL.md 中把 Vitest 的 API 参考分成 Core / Features / Advanced 三层其中 Describe API 被列为 Core 六项之一“describe/suite for grouping tests and nested suites”对应参考文件即 core-describe.md。该参考是基于 Vitest 3.x 于 2026-01-28 生成的见 GENERATION.md而本仓在 pnpm-workspace.yaml 中通过 catalog 将vitest统一钉在^4.1.4——describe这套 API 在 3.x 与 4.x 间保持兼容参考内容可直接用于本仓的测试代码。describe的核心职责是把相关测试归入一个“套件suite”用于组织代码与共享 setup。最基本的用法import { describe, expect, test } from vitest describe(Math, () { test(adds numbers, () { expect(1 1).toBe(2) }) test(subtracts numbers, () { expect(3 - 1).toBe(2) }) }) // 别名suite import { suite } from vitest suite(equivalent to describe, () {})要点顶层的test并不强制包在describe里——它们属于一个隐式的“文件套件”file suitesuite是describe的完全等价别名可按团队命名偏好选择。嵌套套件用套件树表达测试结构当行为随状态变化时例如“登录/未登录”嵌套套件比长字符串的测试名更清晰describe(User, () { describe(when logged in, () { test(shows dashboard, () {}) test(can update profile, () {}) }) describe(when logged out, () { test(shows login page, () {}) }) })嵌套的语义有两点需要记住选项继承子套件会继承父套件的 optionstimeout、retry等Hooks 作用域beforeAll/afterEach等钩子只对其所在套件及嵌套子套件生效跨分支互不干扰。套件级 options一次设置全组生效把配置写在套件上组内所有测试自动继承避免逐条重复// 组内所有测试继承该超时 describe(slow tests, { timeout: 30_000 }, () { test(test 1, () {}) // 30s 超时 test(test 2, () {}) // 30s 超时 })timeout只是可传入 options 的代表之一retry等执行控制项同样适用且会被嵌套子套件继续继承。对于本仓这类涉及网络请求的组件测试Studio 中有大量 mock API 的.test.tsx把超时收敛到套件级别是控制 CI 抖动成本的常用手段。套件修饰符控制“跑不跑、怎么跑”Vitest 在describe上提供了与test对齐的一组修饰符覆盖跳过、聚焦、占位、并发与乱序五类执行控制。跳过套件skip与条件跳过describe.skip(skipped suite, () { test(wont run, () {}) }) // 条件跳过 / 条件运行 describe.skipIf(process.env.CI)(not in CI, () {}) describe.runIf(!process.env.CI)(only local, () {})skipIf/runIf接收一个布尔表达式或同步函数在运行时决定整个套件是否参与执行。典型场景是把只在本地 mock server 可用时才成立的集成用例从 CI 中剔除同时保留代码不被注释废弃。聚焦套件onlydescribe.only(only this suite runs, () { test(runs, () {}) })存在only时只运行被聚焦的测试/套件。注意这是调试工具提交前应移除否则会悄悄缩小测试覆盖面。占位套件tododescribe.todo(implement later)用于登记尚未实现的测试区域让套件树对“欠账”可见。并发套件concurrent与嵌套sequential// 组内测试并行执行 describe.concurrent(parallel tests, () { test(test 1, async ({ expect }) {}) test(test 2, async ({ expect }) {}) })并发套件的注意事项测试间必须相互独立不共享可变状态使用上下文解构出来的expect即async ({ expect })来做快照断言保证快照归属到具体测试组内如确有依赖顺序的步骤可以嵌套describe.sequential把这一段“串行化”describe.concurrent(parallel, () { test(concurrent 1, async () {}) describe.sequential(must be sequential, () { test(step 1, async () {}) test(step 2, async () {}) }) })乱序套件shuffledescribe.shuffle(random order, () { test(test 1, () {}) test(test 2, () {}) test(test 3, () {}) }) // 等价写法通过 options describe(random, { shuffle: true }, () {})随机顺序能尽早暴露“测试之间有隐式顺序依赖”的问题。乱序基于sequence.seed配置生成因此同一 seed 下结果可复现排查失败时可以固定种子重现同一顺序。参数化套件describe.each与describe.for当同一组断言要在多组输入上重复时参数化套件比复制粘贴更合适。Vitest 提供两种写法describe.each对象数组 模板串describe.each([ { name: Chrome, version: 100 }, { name: Firefox, version: 90 }, ])($name browser, ({ name, version }) { test(has version, () { expect(version).toBeGreaterThan(0) }) })模板串中的$name会被替换为每组数据里对应字段的值生成如 “Chrome browser”“Firefox browser” 的子套件名。describe.for元组数组 printf 风格占位符describe.for([ [Chrome, 100], [Firefox, 90], ])(%s browser, ([name, version]) { test(has version, () { expect(version).toBeGreaterThan(0) }) })%s依次对应元组中的位置参数适合参数少、无需字面命名的场景。本仓真实用例Studio 的参数化测试Supabase 单仓中已在使用describe.each。例如 Studio 的计费面板组件测试 PlanUpdateSidePanel.test.tsxdescribe.each([fullscreen, fullscreen-gaps])(%s variant, (flag) { // 对每个布局变体重复同一组断言 })同一文件中第 359 行还有一处describe.each([control, parity, gaps])校验不同变体保持 sheet 外壳结构一致而 logs-query.test.tsx 则用describe.each([free, pro, team, enterprise])(upgrade modal for %s, ...)把升级弹窗的断言铺到四个订阅档位上。这些用例的共同模式是用扁平的字符串数组 %s模板名把“同一逻辑 × N 个输入”压缩成一个可维护的套件——这正是参数化套件在本仓中的典型用法。套件内的 Hooks共享 setup 的边界describe与 hooks 配合是最常见的共享资源模式。以下示例展示“套件级创建、测试级清理”的完整生命周期describe(Database, () { let db beforeAll(async () { db await createDb() }) afterAll(async () { await db.close() }) beforeEach(async () { await db.clear() }) test(insert works, async () { await db.insert({ name: test }) expect(await db.count()).toBe(1) }) })钩子语义与套件作用域强绑定beforeAll/afterAll在套件首/末各执行一次适合昂贵资源的创建与销毁beforeEach/afterEach在每个测试前后执行适合状态重置钩子只对当前套件及其嵌套子套件生效——这正是“嵌套时各分支独立 setup”的结构保证。修饰符可自由链式组合所有修饰符可任意叠加顺序不影响语义describe.skip.concurrent(skipped concurrent, () {}) describe.only.shuffle(only and shuffled, () {}) describe.concurrent.skip(equivalent, () {})即“跳过 并发”“聚焦 乱序”等组合都能表达。实践中建议把组合控制在两类修饰符以内避免意图含糊。关键要点速查与在 monorepo 中运行把整份参考收敛为五条可直接执行的结论顶层测试属于隐式的文件套件不必强行包一层describe嵌套套件继承父级 optionstimeout、retry等子级可覆盖Hooks 的作用域严格限定在其所在套件及嵌套子套件内describe.concurrent下做快照断言时使用上下文里的expectshuffle的随机顺序由sequence.seed配置决定可复现。在 Supabase 这个 pnpm workspace 里vitest版本由 pnpm-workspace.yaml 的 catalog 统一钉在^4.1.4各包的package.json以vitest: catalog:引用如 packages/ui/package.json 的test: vitest脚本、apps/studio/package.json 的依赖声明。因此查看本文所述 API 的落地形态最快的路径是读参考文件 core-describe.md对照同目录下的 core-test-api.md 与 core-hooks.md 补齐test与 hooks 的对应修饰符到apps/studio与packages/ui等包的*.test.tsx中检索describe.each等模式验证参数化套件在真实业务测试中的写法。【免费下载链接】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),仅供参考