关于 vitest-unit-testing
vitest-unit-testing 是使用 Vitest 编写精简 TypeScript 单元测试的技能,面向在现有项目中编写测试的开发者。它强调检验可观察契约、遵循项目惯例,并选择合适的环境与验证命令。
主要功能
- 01检查项目测试配置
- 02选择聚焦的测试边界
- 03用 AAA 组织独立测试
- 04管理模拟与清理
- 05运行定向 Vitest 检查
适用场景
- 01为工具函数添加回归测试
- 02测试服务或 API 客户端行为
- 03为测试文件选择运行环境
- 04调查疑似不稳定的测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ perdolique/ workflow --skill vitest-unit-testing安装后在 Claude Code 中输入/vitest-unit-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonevals/files/performance.mdevals/files/unit-project/package.jsonvitest.config.tsevals/files/unit-project/src/browser-storage.tsfee.tsguests.tspreferences.tstotal.tsevals/files/unit-project/src/__tests__/fee.test.tspreferences.test.tstotal.test.tsreferences/mocking.mdperformance.md详细介绍
使用 Vitest 进行单元测试
使用 Vitest 5 编写有针对性的 TypeScript 单元测试。测试可观察到的契约,确保每个测试相互独立,并遵循目标项目已有的约定。
了解项目背景
编辑测试前,请检查目标项目的:
- Vitest 版本和支持的 Node.js 版本
- package 脚本、测试根目录、配置文件和命名项目
- 测试环境,例如 Node、happy-dom、jsdom 或 Browser Mode,以及每项测试加载的设置文件
- 清理设置,尤其是
clearMocks、mockReset和restoreMocks - 项目本地的命名、文件放置、测试固件、辅助函数和断言风格
本技能面向 Vitest 5。若项目使用较早版本,请相应调整 API 和默认设置。仅凭更改测试的请求,并不代表可以升级依赖。
选择测试边界和环境
- 对工具函数、服务、存储、转换逻辑和 API 客户端编写 TypeScript 单元测试时,使用本技能,也包括这些测试使用的模拟对象和测试固件。根据任务和项目现有设置选择测试工具;仅提到模拟对象或测试固件的一般请求,并不能确定所用工具。
- 明确可观察到的契约,以及测试应捕获的潜在故障。添加测试用例前,先查看附近的测试。选择能够执行相关判断的最小公共边界:直接调用纯函数;若状态或协作者构成契约,则使用存储或服务。只有在渲染或组件交互与测试相关时,才挂载组件。
- 保持作出被测判断的模块为真实模块。例如,基于已保存的客人年龄编写费用测试时,需要使用真实的年龄转人数计算逻辑;通过模拟对象提供人数,就无法测试该规则。对受测决策之外的协作者进行模拟。确保测试相互独立且结果确定。
- 如果测试、其传递导入和设置均不需要浏览器 API,则使用 Node。遵循项目的环境约定;在文件级添加
// @vitest-environment node,可以避免 DOM 设置,而无需修改其他文件。若契约需要 DOM 或浏览器环境,则保留适当的环境。 - 浏览器测试应覆盖独立于此处已涵盖的计算逻辑之外的浏览器相关风险,例如渲染结果、请求接线、焦点状态或跨导航持久性。在对应边界上保留这些覆盖。
放置和命名测试
遵循项目现有结构。若项目没有更明确的约定:
- 将测试放在相邻的
__tests__/目录中。 - 测试文件以源文件命名,并使用
.test.ts后缀,例如config.ts和__tests__/config.test.ts。 - 使用
describe将相关行为分组,并在每项测试名称中明确说明预期结果。
使用 AAA 结构组织测试
每项测试都应清晰划分 Arrange、Act 和 Assert 部分。
import { describe, expect, test } from 'vitest';
import { calculateTotal } from '../calculate-total';
describe(calculateTotal, () => {
test('returns the total price for all item quantities', () => {
// Arrange
const items = [
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 }
];
// Act
const total = calculateTotal(items);
// Assert
expect(total).toBe(250);
});
});
同一契约适用于多个输入时,使用 test.each:
test.each([
['production', 'https://app.example.com'],
['staging', 'https://staging.example.com'],
['development', 'http://localhost:3000']
])('returns the base URL for %s', (environment, expected) => {
const url = getBaseUrl(environment);
expect(url).toBe(expected);
});
选择契约用例
- 覆盖正常行为,以及能够保护不同契约风险的错误、边缘或边界情况。若现有测试已能捕获相同故障,就不要为每个源代码分支都添加一个用例。当代码负责校验输入时,例如在外部数据边界处,应包含
null、undefined、空值和无效类型。对于接收已校验值的内部类型化代码,应使用受支持的输入,不要臆造不受支持的情况。 - 有效的替代测试应能在受保护行为被移除时失败。每项测试都至少要有一个用于验证结果的断言,可直接断言,也可通过断言辅助函数完成。若
toBe、toStrictEqual、toHaveLength、toHaveBeenCalledWith和toHaveBeenCalledTimes等精确断言能够表达契约,应优先使用。
有意模拟并清理
请阅读模拟指南,了解模块模拟、间谍、条件行为、计时器、异步失败和清理。
在 Vitest 5 中,clearMocks 默认为 true,因此每个测试开始前都会清除调用历史,但会保留模拟实现。mockReset 和 restoreMocks 默认为 false。
检查当前配置,并且只添加测试所修改状态所需的清理操作:
-
如果测试修改了某个模拟的实现,而后续测试不应继承该实现,请重置该模拟。
-
如果
restoreMocks已禁用,请还原手动创建的vi.spyOn间谍。 -
使用假计时器的测试或测试套件结束后,请恢复为真实计时器。
-
将提升的
vi.mock、vi.unmock和vi.hoisted调用放在模块级别。Vitest 5 不允许在函数、代码块、钩子以及测试或测试套件的回调中调用这些方法。只有当测试需要在后续动态导入中使用非提升行为时,才使用vi.doMock或vi.doUnmock。
处理异步行为
等待异步断言完成,包括 resolves、rejects、expect.poll 和异步快照匹配器。Vitest 5 会将未等待的异步断言判定为失败。
当计时器回调会调度 Promise 时,请使用异步计时器控制:
await vi.advanceTimersByTimeAsync(1_000);
当计时器触发拒绝时,请先创建拒绝断言,再推进时间,随后等待这两个操作。这样可在 Promise 完成前附加拒绝处理程序。
保持 TypeScript 检查有效
- 使用
vi.mocked()以类型安全的方式访问模拟函数。 - 使用
satisfies根据已知契约检查测试固件和预期载荷,同时不扩大字面量类型。 - 当测试有意传入无效类型以验证运行时校验时,请使用
@ts-expect-error。 - 对部分模块工厂使用带类型的
vi.mock(import(...))签名;其导出项为可选,并且返回类型仍会受到检查。
运行相关测试
如果目标项目的包脚本定义了正确的单元测试命令,请使用该脚本。从拥有相关配置的项目根目录,以非交互模式运行 Vitest。如果项目没有合适的脚本,请直接使用 vpx vitest run 运行该包。
# Project script, with a focused file when the script forwards arguments
pnpm run test:unit -- src/utils/__tests__/config.test.ts
# Direct package execution from the root that owns the config
vpx vitest run src/utils/__tests__/config.test.ts --config ./vitest.config.ts --project unit
- 选择覆盖变更契约及其相关模拟或测试固件的文件、目录或命名项目。如果变更可能影响更多测试,请扩大运行范围。Vitest 5 不会向上查找父目录中的配置,因此请从正确的根目录运行,或显式传入
--config,并根据需要限定发现范围。 - 如果任务需要覆盖率证据,请使用覆盖率功能。使用
--repeats调查疑似不稳定的测试。
优化测试性能
如果任务涉及测试运行时间、工作线程或内存设置,或共享初始化成本,请阅读性能指南。普通测试变更只需完成上述相关验证;重复对比运行则属于性能调查。
参考文件
- references/mocking.md — 模块模拟、间谍、返回行为、计时器、清理、断言和常见依赖模式
- references/performance.md — 查找初始化成本、比较运行时设置,以及测量时间和资源使用情况