Vitest 4.1 发布
2026 年 3 月 12 日

下一个 Vitest 小版本来了
今天,我们很高兴地宣布 Vitest 4.1 发布,包含许多令人兴奋的新功能!
快速链接:
如果你之前没有使用过 Vitest,我们建议先阅读 入门指南 和 功能 指南。
我们向 713 位 Vitest Core 贡献者 以及帮助我们开发此新版本的 Vitest 集成、工具和翻译的维护者和贡献者表示感谢。我们鼓励你参与进来,帮助我们为整个生态系统改进 Vitest。在我们的 贡献指南 中了解更多。
为了开始,我们建议帮助 分类 issue,审查 PR,基于开放问题发送失败测试的 PR,并在 讨论区 和 Vitest Land 的 帮助论坛 中支持他人。如果你想与我们交谈,加入我们的 Discord 社区 并在 #contributing 频道 打招呼。
有关 Vitest 生态系统和 Vitest core 的最新消息,请在 Bluesky 或 Mastodon 上关注我们。
为了保持更新,请关注 VoidZero 博客 并订阅 新闻通讯。
Vite 8 支持
此版本添加了对新 Vite 8 版本的支持。此外,如果可能,Vitest 现在使用安装的 vite 版本,而不是下载单独的依赖。这使得配置文件中的类型不一致等问题不再出现。
测试标签
标签 允许你标记测试以将它们组织成组。一旦标记,你可以按标签过滤测试或对具有给定标签的每个测试应用共享选项——如更长的超时或自动重试。
要使用标签,请在配置文件中定义它们。每个标签需要一个 name,并且可以可选地包含适用于标记了该标签的每个测试的测试选项。有关可用选项的完整列表,请参阅 tags。
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
tags: [
{
name: 'db',
description: '数据库查询测试。',
timeout: 60_000,
},
{
name: 'flaky',
description: '不稳定的 CI 测试。',
retry: process.env.CI ? 3 : 0,
},
],
},
})使用此配置,你可以将 flaky 和 db 标签应用于你的测试:
test('flaky database test', { tags: ['flaky', 'db'] }, () => {
// ...
})该测试的超时时间为 60 秒,并且在 CI 上将重试 3 次,因为这些选项是在配置文件中为 db 和 flaky 标签指定的。
受 pytest 启发,Vitest 支持用于过滤标签的自定义语法:
and或&&包含两个表达式or或||包含至少一个表达式not或!排除表达式*匹配任意数量的字符(0 个或多个)()分组表达式并覆盖优先级
以下是一些常见的过滤模式:
# 仅运行单元测试
vitest --tags-filter="unit"
# 运行既是前端又是快速的测试
vitest --tags-filter="frontend and fast"
# 运行非不稳定的前端测试
vitest --tags-filter="frontend && !flaky"
# 运行匹配通配符模式的测试
vitest --tags-filter="api/*"实验性 viteModuleRunner: false
默认情况下,Vitest 在 Vite 的 模块运行器 内部运行所有代码——这是一个宽松的沙箱,提供 import.meta.env、require、__dirname、__filename,并应用 Vite 插件和别名。虽然这使得入门变得容易,但它可能会隐藏真实问题:你的测试可能在沙箱中通过,但在生产环境中失败,因为运行时行为与原生 Node.js 不同。
Vitest 4.1 引入了 experimental.viteModuleRunner,它允许你完全禁用模块运行器,而是使用原生 import 运行测试:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
experimental: {
viteModuleRunner: false,
},
},
})使用此标志,不应用任何文件转换——你的测试文件、源代码和设置文件由 Node.js 直接执行。这意味着更快的启动,更接近生产环境的行为,并且像不正确的 __dirname 注入或静默通过的不存在导出导入等问题会被早期捕获。
如果你使用的是 Node.js 22.18+ 或 23.6+,TypeScript 会被 原生剥离——不需要额外配置。
支持通过 Node.js Module Loader API 使用 vi.mock 和 vi.hoisted 进行 Mock(需要 Node.js 22.15+)。注意,import.meta.env、Vite 插件、别名和 istanbul 覆盖率提供程序在此模式下不可用。
如果你运行不需要 Vite 转换的服务器端或脚本类测试,请考虑此选项。对于 jsdom/happy-dom 测试,我们仍然推荐默认模块运行器或 浏览器模式。
在 experimental.viteModuleRunner 文档 中阅读更多。
配置 UI 浏览器窗口
Vitest 4.1 引入了 browser.detailsPanelPosition,让你选择详情面板在浏览器 UI 中出现的位置。
这在较小屏幕上特别有用,切换到底部面板可以为你的应用留下更多水平空间:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
browser: {
enabled: true,
detailsPanelPosition: 'bottom', // 或 'right'
},
},
})你也可以通过新的布局切换按钮直接从 UI 切换此设置。
增强的浏览器 Trace 视图
Vitest 4.1 为浏览器模式中的 Playwright Trace 查看器 集成带来了重大改进。像 click、fill 和 expect.element 这样的浏览器交互现在会在 trace 时间线中自动分组,并链接回测试文件中的确切行。
框架库也在集成 trace。例如,vitest-browser-react 的 render() 工具现在会自动出现在 trace 中,并高亮显示渲染元素。
对于自定义注解,新的 page.mark 和 locator.mark API 让你将自己的标记添加到 trace 中:
import { page } from 'vitest/browser'
await page.mark('before sign in')
await page.getByRole('button', { name: 'Sign in' }).click()
await page.mark('after sign in')你也可以将整个流程分组在一个命名条目下:
await page.mark('sign in flow', async () => {
await page.getByRole('textbox', { name: 'Email' }).fill('john@example.com')
await page.getByRole('textbox', { name: 'Password' }).fill('secret')
await page.getByRole('button', { name: 'Sign in' }).click()
})在 Trace 查看指南 中阅读更多。
test.extend 中的类型推断 - 新的构建器模式
Vitest 4.1 引入了一种新的 test.extend 模式,支持类型推断。你可以从工厂返回值,而不是调用 use 函数——TypeScript 从其返回值推断每个 fixture 的类型,所以你不需要手动声明类型。
import { test as baseTest } from 'vitest'
export const test = baseTest
// 简单值 - 类型推断为 { port: number; host: string }
.extend('config', { port: 3000, host: 'localhost' })
// 函数 fixture - 类型从返回值推断
.extend('server', async ({ config }) => {
// TypeScript 知道 config 是 { port: number; host: string }
return `http://${config.host}:${config.port}`
})对于需要设置或清理逻辑的 fixture,请使用函数。onCleanup 回调注册 teardown 逻辑,该逻辑在 fixture 的作用域结束后运行:
import { test as baseTest } from 'vitest'
export const test = baseTest
.extend('tempFile', async ({}, { onCleanup }) => {
const filePath = `/tmp/test-${Date.now()}.txt`
await fs.writeFile(filePath, 'test data')
// 注册清理 - 测试完成后运行
onCleanup(() => fs.unlink(filePath))
return filePath
})除此之外,Vitest 现在将 file 和 worker 上下文传递给 beforeAll、afterAll 和 aroundAll 钩子:
import { test as baseTest } from 'vitest'
const test = baseTest
.extend('config', { scope: 'file' }, () => loadConfig())
.extend('db', { scope: 'file' }, ({ config }) => createDatabase(config.port))
test.beforeAll(async ({ db }) => {
await db.migrateUsers()
})
test.afterAll(async ({ db }) => {
await db.deleteUsers()
})WARNING
此更改可能被视为破坏性变更。此前 Vitest 将未文档化的 Suite 作为第一个参数传递。团队认为使用情况足够少,不会破坏生态系统。
新的 aroundAll 和 aroundEach 钩子
新的 aroundEach 钩子注册一个回调函数,该函数包裹当前套件中的每个测试。回调接收一个 runTest 函数,必须调用该函数以运行测试。aroundAll 钩子的工作方式类似,但它针对每个套件调用,而不是每个测试。
当你的测试需要在上下文中运行时,你应该使用 aroundEach,例如:
- 将测试包裹在 AsyncLocalStorage 上下文中
- 使用追踪 span 包裹测试
- 数据库事务
import { test as baseTest } from 'vitest'
const test = baseTest
.extend('db', async ({}, { onCleanup }) => {
// db 在 `aroundEach` 钩子之前创建
const db = await createTestDatabase()
onCleanup(() => db.close())
return db
})
test.aroundEach(async (runTest, { db }) => {
await db.transaction(runTest)
})
test('insert user', async ({ db }) => {
// 在事务内部调用
await db.insert({ name: 'Alice' })
})用于更好堆栈跟踪的辅助函数
当测试在共享工具函数内部失败时,堆栈跟踪通常指向该辅助函数内部的行——而不是调用它的地方。这使得很难找到实际失败的测试,尤其是当同一个辅助函数在许多测试中使用时。
vi.defineHelper 包装了一个函数,以便 Vitest 从其堆栈跟踪中移除其内部结构,并将错误指向调用站点:
import { expect, test, vi } from 'vitest'
const assertPair = vi.defineHelper((a, b) => {
expect(a).toEqual(b) // 🙅♂️ 错误代码块不会指向这里
})
test('example', () => {
assertPair('left', 'right') // 🙆 但会指向这里
})这对于自定义断言库和可重用的测试工具特别有用,因为调用站点比实现更有意义。
使用 --detect-async-leaks 捕获泄漏
泄漏的计时器、句柄和未解决的异步资源会使测试套件变得不稳定且难以调试。Vitest 4.1 添加了 detectAsyncLeaks 以帮助跟踪这些问题。
你可以通过 CLI 启用它:
vitest --detect-async-leaks或在配置中:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
detectAsyncLeaks: true,
},
})启用后,Vitest 使用 node:async_hooks 报告带有源位置的泄漏异步资源。由于这会增加运行时开销,最好在调试时使用。
vscode 改进
官方 vscode 扩展 收到了大量修复和新功能:
- 除非你手动启用连续运行或通过新配置选项
watchOnStartup显式启用,否则扩展不再在后台保持运行进程。这减少了内存使用并消除了maximumConfigs配置选项。 - 新的 "Run Related Tests" 命令运行导入当前打开文件的测试。
- 点击沟槽图标时,现在可以使用新的 "Toggle Continuous Run" 操作。
- 扩展现在支持 Deno 运行时。
- 点击 "Stop" 后,扩展会尽可能更快地取消测试运行。
- 如果你使用 Vitest 4.1,扩展会在每个导入语句旁边内联显示模块加载时间。
GitHub Actions 任务摘要
内置的 github-actions 报告器 现在会自动生成一个 任务摘要,概述你的测试结果。摘要包括测试文件和测试用例统计信息,并突出显示需要重试的不稳定测试——带有永久链接 URL,将测试名称直接链接到 GitHub 上的相关源行。
在 GitHub Actions 中运行时,默认启用摘要并写入 $GITHUB_STEP_SUMMARY 指定的路径。大多数情况下无需配置。要禁用它或自定义输出路径:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
reporters: [
['github-actions', {
jobSummary: {
enabled: false, // 或设置 `outputPath` 以自定义摘要写入的位置
},
}],
],
},
})新的 agent 报告器以减少 Token 使用量
随着 AI 编码代理成为运行测试的常见方式,Vitest 4.1 引入了 agent 报告器 —— 一种旨在减少 Token 使用量的最小输出模式。它仅显示失败的测试及其错误,抑制通过的测试输出和通过测试的控制台日志。
当 Vitest 检测到它在 AI 编码代理内部运行时,会自动启用此报告器。检测由 std-env 提供支持,它可以识别流行的代理环境。你也可以显式设置 AI_AGENT=copilot(或任何名称)环境变量。无需配置——只需像往常一样运行 Vitest:
AI_AGENT=copilot vitest如果你配置了自定义报告器,自动检测将被跳过,因此如果你想要两者,请手动将 'agent' 添加到列表中。
新的 mockThrow API
以前,使 mock 抛出错误需要将错误包装在函数中:mockImplementation(() => { throw new Error(...) })。新的 mockThrow 和 mockThrowOnce 方法使这更简洁易读:
const myMockFn = vi.fn()
myMockFn.mockThrow(new Error('error message'))
myMockFn() // 抛出 Error<'error message'>WebdriverIO 和 Preview 中的严格模式
在 webdriverio 和 preview 中,定位元素现在默认是严格的,与 Playwright 行为匹配。
如果定位器解析为多个元素,Vitest 会抛出“严格模式违规”而不是静默选择一个。这有助于尽早捕获模糊查询:
const button = page.getByRole('button')
await button.click() // 如果匹配到多个按钮则抛出错误
await button.click({ strict: false }) // 退出严格模式并返回第一个匹配项Chai 风格的 Mock 断言
Vitest 已经支持 chai 风格的断言,如 eql、throw 和 be。此版本将该支持扩展到 mock 断言,使得从基于 Sinon 的测试套件迁移更容易,而无需重写每个期望:
import { expect, vi } from 'vitest'
const fn = vi.fn()
fn('example')
expect(fn).to.have.been.called // expect(fn).toHaveBeenCalled()
expect(fn).to.have.been.calledWith('example') // expect(fn).toHaveBeenCalledWith('example')
expect(fn).to.have.returned // expect(fn).toHaveReturned()
expect(fn).to.have.callCount(1) // expect(fn).toHaveBeenCalledTimes(1)覆盖率 ignore start/stop 忽略提示
你现在可以使用 ignore start/stop 注释完全忽略代码覆盖率中的特定行。 在 Vitest v3 中,v8 提供者支持此功能,但由于底层依赖项更改,v4.0 中不支持。
由于社区的请求,我们现在自己实现了它,并将支持扩展到 v8 和 istanbul 提供者。
/* istanbul ignore start -- @preserve */
if (parameter) {
console.log('Ignored')
}
else {
console.log('Ignored')
}
/* istanbul ignore stop -- @preserve */
console.log('Included')
/* v8 ignore start -- @preserve */
if (parameter) {
console.log('Ignored')
}
else {
console.log('Ignored')
}
/* v8 ignore stop -- @preserve */
console.log('Included')查看更多示例请参阅 覆盖率 | 忽略代码。
仅针对变更文件的覆盖率
如果你只想获取修改文件的代码覆盖率,可以使用 coverage.changed 来限制文件包含。
与常规的 --changed 标志相比,--coverage.changed 允许你仍然运行所有测试文件,但将覆盖率报告限制为仅变更的文件。 这允许你从覆盖率中排除 --changed 否则会包含的未变更文件。
HTML 报告器和子路径部署中的覆盖率
覆盖率 HTML 查看现在可以在 UI 模式、HTML 报告器和浏览器模式中可靠地工作——包括在子路径下部署时。对于自定义覆盖率报告器,可以使用新的 coverage.htmlDir 选项来集成他们的 HTML 输出。
致谢
Vitest 4.1 是 Vitest 团队 和我们的贡献者无数小时工作的结果。我们感谢赞助 Vitest 开发的个人和公司。Vladimir 和 Hiroshi 是 VoidZero 团队的一部分,能够全职从事 Vite 和 Vitest 工作,而 Ari 得益于 Chromatic 的支持,可以投入更多时间在 Vitest 上。非常感谢 Zammad,以及 Vitest 的 GitHub Sponsors 和 Vitest 的 Open Collective 上的赞助商。
