迁移指南
迁移到 Vitest 2.0
默认池类型为 forks
Vitest 2.0 将 pool
的默认配置更改为 'forks'
,以提高稳定性。你可以在 PR 中阅读完整的动机。
如果你在未指定 pool
的情况下使用了 poolOptions
,你可能需要更新配置:
export default defineConfig({
test: {
poolOptions: {
threads: {
singleThread: true,
},
forks: {
singleFork: true,
},
},
},
});
钩子以栈的方式运行
在 Vitest 2.0 之前,所有钩子都是并行运行的。在 2.0 中,所有钩子都是串行运行的。此外,afterAll
/afterEach
钩子以相反的顺序运行。
要恢复钩子的并行执行,请将 sequence.hooks
设置为 'parallel'
:
export default defineConfig({
test: {
sequence: {
hooks: 'parallel',
},
},
});
suite.concurrent
并发运行所有测试
以前,在套件上指定 concurrent
会按套件对并发测试进行分组,并按顺序运行它们。现在,遵循 Jest 的行为,所有测试都并发运行(受 maxConcurrency
限制)。
V8 覆盖率选项 coverage.ignoreEmptyLines
默认启用
coverage.ignoreEmptyLines
的默认值现在为 true。这一重大更改可能会影响代码覆盖率报告,需要为某些项目调整覆盖率阈值。此调整仅影响 coverage.provider
为 'v8'
时的默认设置。
移除 watchExclude
选项
Vitest 使用 Vite 的监听器。通过将文件或目录添加到 server.watch.ignored
来排除它们:
export default defineConfig({
server: {
watch: {
ignored: ['!node_modules/examplejs'],
},
},
});
--segfault-retry
已移除
随着默认池的更改,不再需要此选项。如果你遇到段错误(segfault)错误,请尝试切换到 'forks'
池。如果问题仍然存在,请打开一个新问题并提供复现步骤。
套件任务中的空任务项已移除
这是对高级 任务 API 的更改。以前,遍历 .suite
最终会指向用于代替文件任务的空内部套件。
这使得 .suite
变为可选;如果任务在顶层定义,则不会有套件。你可以转而使用现在所有任务(包括文件任务本身)都存在的 .file
属性,但请注意避免无限递归。
此更改还从 expect.getState().currentTestName
中移除了文件,并使 expect.getState().testPath
成为必需。
task.meta
已添加到 JSON 报告器
JSON 报告器现在会为每个断言结果输出 task.meta
。
简化模拟函数的泛型类型(例如 vi.fn<T>
、Mock<T>
)
以前 vi.fn<TArgs, TReturn>
分别使用两个泛型类型表示参数和返回值。现在更改为直接接受函数类型 vi.fn<T>
以简化用法。
import { type Mock, vi } from 'vitest';
const add = (x: number, y: number): number => x + y;
// 使用 vi.fn<T>
const mockAdd = vi.fn<Parameters<typeof add>, ReturnType<typeof add>>();
const mockAdd = vi.fn<typeof add>();
// 使用 Mock<T>
const mockAdd: Mock<Parameters<typeof add>, ReturnType<typeof add>> = vi.fn();
const mockAdd: Mock<typeof add> = vi.fn();
访问已解析的 mock.results
以前,如果函数返回 Promise,Vitest 会解析 mock.results
中的 Promise 值。现在有一个单独的 mock.settledResults
属性,它仅在返回的 Promise 被解析或拒绝时才填充。
const fn = vi.fn().mockResolvedValueOnce('result');
await fn();
const result = fn.mock.results[0]; // 'result'
const result = fn.mock.results[0]; // 'Promise<result>'
const settledResult = fn.mock.settledResults[0]; // 'result'
通过此更改,我们还引入了新的 toHaveResolved*
匹配器,类似于 toHaveReturned
,以便在你之前使用 toHaveReturned
时更容易迁移:
const fn = vi.fn().mockResolvedValueOnce('result');
await fn();
expect(fn).toHaveReturned('result');
expect(fn).toHaveResolved('result');
浏览器模式
Vitest 浏览器模式在 Beta 周期中发生了许多变化。你可以在 GitHub 讨论页面 上阅读我们关于浏览器模式的理念。
大多数更改都是新增功能,但也有一些小的破坏性更改:
已移除的弃用选项
一些弃用选项已移除:
vitest typecheck
命令 - 请改用vitest --typecheck
VITEST_JUNIT_CLASSNAME
和VITEST_JUNIT_SUITE_NAME
环境变量(请改用报告器选项)- 检查
c8
覆盖率(请改用 coverage-v8) - 从
vitest
导出SnapshotEnvironment
- 请改从vitest/snapshot
导入 SpyInstance
已移除,取而代之的是MockInstance
迁移到 Vitest 1.0
最低要求
Vitest 1.0 需要 Vite 5.0 和 Node.js 18 或更高版本。
所有 @vitest/*
子包都需要 Vitest 1.0 版本。
快照更新 #3961
快照中的引号不再转义,所有快照都使用反引号(`),即使字符串只有一行。
- 引号不再转义:
expect({ foo: 'bar' }).toMatchInlineSnapshot(`
Object {
- \\"foo\\": \\"bar\\",
+ "foo": "bar",
}
`)
- 单行快照现在使用“`”引号而不是“'”:
- expect('some string').toMatchInlineSnapshot('"some string"')
+ expect('some string').toMatchInlineSnapshot(`"some string"`)
@vitest/snapshot
包也有更改。如果你没有直接使用它,则无需更改任何内容。
- 你不再需要仅仅为了重写
equalityCheck
方法而扩展SnapshotClient
:只需在实例化时将其作为isEqual
传递即可 client.setTest
已重命名为client.startCurrentRun
client.resetCurrent
已重命名为client.finishCurrentRun
池标准化 #4172
我们移除了许多配置选项,以便更轻松地根据你的需求配置测试运行器。如果你依赖 --threads
或其他相关标志,请查看迁移示例。
--threads
现在是--pool=threads
--no-threads
现在是--pool=forks
--single-thread
现在是--poolOptions.threads.singleThread
--experimental-vm-threads
现在是--pool=vmThreads
--experimental-vm-worker-memory-limit
现在是--poolOptions.vmThreads.memoryLimit
--isolate
现在是--poolOptions.<pool-name>.isolate
和browser.isolate
test.maxThreads
现在是test.poolOptions.<pool-name>.maxThreads
test.minThreads
现在是test.poolOptions.<pool-name>.minThreads
test.useAtomics
现在是test.poolOptions.<pool-name>.useAtomics
test.poolMatchGlobs.child_process
现在是test.poolMatchGlobs.forks
test.poolMatchGlobs.experimentalVmThreads
现在是test.poolMatchGlobs.vmThreads
{
scripts: {
- "test": "vitest --no-threads"
// 对于相同的行为:
+ "test": "vitest --pool forks --poolOptions.forks.singleFork"
// 或者多并行 fork:
+ "test": "vitest --pool forks"
}
}
{
scripts: {
- "test": "vitest --experimental-vm-threads"
+ "test": "vitest --pool vmThreads"
}
}
{
scripts: {
- "test": "vitest --isolate false"
+ "test": "vitest --poolOptions.threads.isolate false"
}
}
{
scripts: {
- "test": "vitest --no-threads --isolate false"
+ "test": "vitest --pool forks --poolOptions.forks.isolate false"
}
}
覆盖率更改 #4265, #4442
选项 coverage.all
现在默认启用。这意味着所有匹配 coverage.include
模式的项目文件都将被纳入覆盖率统计,即使它们没有被执行。
覆盖率阈值 API 的结构已更改,现在支持使用 glob 模式为特定文件指定阈值:
export default defineConfig({
test: {
coverage: {
- perFile: true,
- thresholdAutoUpdate: true,
- 100: true,
- lines: 100,
- functions: 100,
- branches: 100,
- statements: 100,
+ thresholds: {
+ perFile: true,
+ autoUpdate: true,
+ 100: true,
+ lines: 100,
+ functions: 100,
+ branches: 100,
+ statements: 100,
+ }
}
}
})
模拟类型 #4400
为了与 Jest 风格的“Mock”命名保持一致,一些类型已被移除。
- import { EnhancedSpy, SpyInstance } from 'vitest'
+ import { MockInstance } from 'vitest'
WARNING
SpyInstance
已弃用,取而代之的是 MockInstance
,并将在下一个主要版本中移除。
定时器模拟 #3925
vi.useFakeTimers()
不再自动模拟 process.nextTick
。 仍然可以通过明确指定 vi.useFakeTimers({ toFake: ['nextTick'] })
来模拟 process.nextTick
。
但是,在使用 --pool=forks
时无法模拟 process.nextTick
。如果你需要 process.nextTick
模拟,请使用不同的 --pool
选项。
从 Jest 迁移
Vitest 的 API 设计兼容 Jest,旨在使从 Jest 迁移尽可能简单。尽管我们为此付出了努力,你可能仍然会遇到以下差异:
默认全局变量
Jest 默认启用其 全局 API。而 Vitest 则不会。你可以通过 globals 配置设置 启用全局变量,或者更新你的代码以使用从 vitest
模块导入。
如果你决定禁用全局变量,请注意,像 testing-library
这样的常用库将不会自动执行 DOM 清理。
模块模拟
在 Jest 中模拟模块时,工厂函数的返回值会作为默认导出。在 Vitest 中,工厂函数需要返回一个对象,其中每个导出都需显式定义。例如,以下 jest.mock
必须更新如下:
jest.mock('./some-path', () => 'hello');
vi.mock('./some-path', () => ({
default: 'hello',
}));
有关更多详细信息,请参阅 vi.mock
API 部分。
自动模拟行为
与 Jest 不同,<root>/__mocks__
中的模拟模块只有在调用 vi.mock()
后才会加载。如果你需要它们在每个测试中都被模拟,就像在 Jest 中一样,你可以在 setupFiles
中模拟它们。
导入模拟包的原始包
如果你只进行部分模块模拟,你可能以前使用过 Jest 的 requireActual
函数。在 Vitest 中,你应该将这些调用替换为 vi.importActual
。
const { cloneDeep } = jest.requireActual('lodash/cloneDeep');
const { cloneDeep } = await vi.importActual('lodash/cloneDeep');
将模拟扩展到外部库
Jest 默认会扩展模块模拟到外部库。在 Vitest 中,当模拟一个模块并希望将此模拟扩展到使用相同模块的其他外部库时,你需要明确指出希望模拟哪个第三方库。这可以通过将外部库添加到 server.deps.inline 配置来实现,使其作为你的源代码的一部分进行处理。
server.deps.inline: ["lib-name"]
expect.getState().currentTestName
Vitest 的 test
名称用 >
符号连接,以便更容易区分测试和套件,而 Jest 使用空格( )。
- `${describeTitle} ${testTitle}`
+ `${describeTitle} > ${testTitle}`
环境变量
就像 Jest 一样,如果 NODE_ENV
之前没有设置,Vitest 会将其设置为 test
。Vitest 也有一个与 JEST_WORKER_ID
对应的环境变量,叫做 VITEST_POOL_ID
(总是小于或等于 maxThreads
)。如果你依赖 JEST_WORKER_ID
,请记得将其重命名。此外,Vitest 还暴露了 VITEST_WORKER_ID
,它是一个正在运行的 worker 的唯一 ID——该数值不受 maxThreads
的影响,并且会随着每个创建的 worker 而递增。
替换属性
如果你想修改对象,在 Jest 中你会使用 replaceProperty API,在 Vitest 中你可以使用 vi.stubEnv
或 vi.spyOn
来实现相同的功能。
Done 回调
自 Vitest v0.10.0 起,已弃用测试的回调式声明方式。你可以将它们重写为使用 async
/await
函数,或者使用 Promise 来模拟回调风格。
it('should work', (done) => { // [!code --]
it('should work', () => new Promise(done => { // [!code ++]
// ...
done()
}) // [!code --]
})) // [!code ++]
钩子
beforeAll
/beforeEach
钩子在 Vitest 中可能返回 teardown 函数。因此,如果你的钩子返回 undefined
或 null
以外的值,你可能需要调整你的钩子声明方式:
beforeEach(() => setActivePinia(createTestingPinia()));
beforeEach(() => {
setActivePinia(createTestingPinia());
});
在 Jest 中,钩子是按顺序调用的。默认情况下,Vitest 并行运行钩子。要使用 Jest 的行为,请更新 sequence.hooks
选项:
export default defineConfig({
test: {
sequence: {
hooks: 'list',
},
},
});
类型
Vitest 没有与 jest
命名空间等效的类型,所以你需要直接从 vitest
导入类型:
let fn: jest.Mock<(name: string) => number>;
import type { Mock } from 'vitest';
let fn: Mock<(name: string) => number>;
定时器
Vitest 不支持 Jest 的旧版定时器。
超时
如果你使用了 jest.setTimeout
,你需要迁移到 vi.setConfig
:
jest.setTimeout(5_000);
vi.setConfig({ testTimeout: 5_000 });
Vue 快照
这不是 Jest 特有的功能,但如果你以前使用 Jest 和 vue-cli preset,你需要安装 jest-serializer-vue
包,并在 setupFiles 中使用它:
import { defineConfig } from 'vite';
export default defineConfig({
test: {
setupFiles: ['./tests/unit/setup.js'],
},
});
import vueSnapshotSerializer from 'jest-serializer-vue';
expect.addSnapshotSerializer(vueSnapshotSerializer);
否则你的快照会有很多转义的“"”字符。