迁移指南
迁移到 Vitest 3.0
测试选项作为第三个参数
Vitest 3.0 在将对象作为 test 或 describe 函数的第三个参数时会打印警告:
test('validation works', () => {
// ...
}, { retry: 3 });
test('validation works', { retry: 3 }, () => {
// ...
});在下一个主要版本中,如果第三个参数是对象,将会抛出错误。请注意,超时时间(数字)未被弃用:
test('validation works', () => {
// ...
}, 1000); // Ok ✅browser.name 和 browser.providerOptions 已弃用
在 Vitest 4 中,browser.name 和 browser.providerOptions 都将被移除。请使用新的 browser.instances 选项:
export default defineConfig({
test: {
browser: {
name: 'chromium',
providerOptions: {
launch: { devtools: true },
},
instances: [
{
browser: 'chromium',
launch: { devtools: true },
},
],
},
},
})通过新的 browser.instances 字段,您还可以指定多个浏览器配置。
spy.mockReset 现在恢复原始实现
以前,在不重新应用 spy 的情况下,没有很好的方法能将 spy 重置为原始实现。现在,spy.mockReset 会将实现函数重置为原始实现,而不是一个假的空操作。
const foo = {
bar: () => 'Hello, world!',
};
vi.spyOn(foo, 'bar').mockImplementation(() => 'Hello, mock!');
foo.bar(); // 'Hello, mock!'
foo.bar.mockReset();
foo.bar(); // undefined
foo.bar(); // 'Hello, world!'vi.spyOn 如果方法已被模拟,则重用模拟
以前,Vitest 在监视一个对象时总是会分配一个新的 spy。这导致 mockRestore 出现错误,因为它会将 spy 恢复到之前的 spy,而不是原始函数:
vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar');
vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar');
vi.restoreAllMocks();
vi.isMockFunction(fooService.foo); // true
vi.isMockFunction(fooService.foo); // false伪造计时器默认值
Vitest 不再为 fakeTimers.toFake 提供默认选项。现在,Vitest 将模拟任何可用的计时器相关 API(nextTick 除外)。具体来说,当调用 vi.useFakeTimers 时,performance.now() 现在会被模拟。
vi.useFakeTimers();
performance.now(); // original
performance.now(); // fake您可以通过在调用 vi.useFakeTimers 时或在全局配置中指定计时器来恢复之前的行为:
export default defineConfig({
test: {
fakeTimers: {
toFake: [
'setTimeout',
'clearTimeout',
'setInterval',
'clearInterval',
'setImmediate',
'clearImmediate',
'Date',
]
},
},
})更严格的错误相等性检查
Vitest 现在在通过 toEqual 或 toThrowError 比较错误时检查更多属性。Vitest 现在会比较 name、message、cause 和 AggregateError.errors。对于 Error.cause,比较是非对称进行的:
expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi')); // ✅
expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' })); // ❌除了检查更多属性外,Vitest 现在还会比较错误原型。例如,如果抛出 TypeError,相等性检查应该引用 TypeError,而不是 Error:
expect(() => {
throw new TypeError('type error');
})
.toThrowError(new Error('type error'))
.toThrowError(new TypeError('type error')); 有关更多详细信息,请参阅 PR:#5876。
Vite 6 中默认不解析 module 条件导出
Vite 6 允许更灵活的 resolve.conditions 选项,而 Vitest 默认配置为排除 module 条件导出。 关于 Vite 方面的更改详情,请参阅 Vite 6 迁移指南。
Custom 类型已弃用 API
Custom 类型现在是 Test 类型的别名。请注意,Vitest 在 2.1 中更新了公共类型,并将导出的名称更改为 RunnerCustomCase 和 RunnerTestCase:
import {
RunnerCustomCase,
RunnerTestCase,
} from 'vitest';如果您正在使用 getCurrentSuite().custom(),返回任务的 type 现在等于 'test'。Custom 类型将在 Vitest 4 中移除。
WorkspaceSpec 类型不再使用 API
在公共 API 中,该类型曾用于自定义 sequencers。请改用 TestSpecification。
onTestFinished 和 onTestFailed 现在接收上下文
onTestFinished 和 onTestFailed 钩子以前接收测试结果作为第一个参数。现在,它们接收测试上下文,就像 beforeEach 和 afterEach 一样。
快照 API 的更改 API
@vitest/snapshot 中的公共快照 API 已更改,以支持单个运行中的多个状态。详情请参阅 PR:#6817
请注意,此更改仅影响直接使用快照 API 的开发人员。.toMatchSnapshot API 没有更改。
resolveConfig 类型签名的更改 API
resolveConfig 现在更有用。它现在接受用户配置而非已解析的 Vite 配置,并返回解析后的配置。
此函数不用于内部,仅作为公共 API 公开。
清理 vitest/reporters 类型 API
vitest/reporters 入口现在仅导出 reporter 实现和选项类型。如果您需要访问 TestCase/TestSuite 和其他任务相关类型,请从 vitest/node 额外导入它们。
即使 coverage.excludes 被覆盖,覆盖率也会忽略测试文件。
通过覆盖 coverage.excludes 来将测试文件包含在覆盖率报告中已不再可能。测试文件现在总是被排除。
迁移到 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 已移除
由于默认池的变更,此选项已不再需要。如果您遇到段错误,请尝试切换到 'forks' 池。如果问题仍然存在,请提交一个新问题并提供复现步骤。
套件任务中的空任务已移除
这是对高级 任务 API 的更改。以前,遍历 .suite 最终会导向一个空的内部套件,该套件用于代替文件任务。
这使得 .suite 成为可选;如果任务在顶层定义,它将没有套件。您可以回退到现在所有任务(包括文件任务本身,因此请注意不要陷入无限递归)都存在的 .file 属性。
此更改还会从 expect.getState().currentTestName 中移除文件,并使 expect.getState().testPath 成为必需。
task.meta 已添加到 JSON Reporter
JSON reporter 现在为每个断言结果打印 task.meta。
简化模拟函数的泛型类型(例如 vi.fn<T>、Mock<T>)
以前 vi.fn<TArgs, TReturn> 分别接受两个泛型类型用于参数和返回值。现已改为直接接受函数类型 vi.fn<T> 以简化用法。
import { vi } from 'vitest';
import type { Mock } from 'vitest';
const add = (x: number, y: number): number => x + y;
// using vi.fn<T>
const mockAdd = vi.fn<Parameters<typeof add>, ReturnType<typeof add>>();
const mockAdd = vi.fn<typeof add>();
// using 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 值。现在有一个单独的 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 讨论页面 上阅读我们关于浏览器模式的理念。
大多数更改都是增量的,但也有一些小的破坏性更改:
noneprovider 已重命名为preview#5842previewprovider 现在是默认值 #5842indexScripts已重命名为orchestratorScripts#5842
已移除的弃用选项
部分已弃用选项已被移除:
vitest typecheck命令 - 请改用vitest --typecheckVITEST_JUNIT_CLASSNAME和VITEST_JUNIT_SUITE_NAME环境变量(请改用 reporter 选项)- 检查
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.startCurrentRunclient.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.isolatetest.maxThreads现在是test.poolOptions.<pool-name>.maxThreadstest.minThreads现在是test.poolOptions.<pool-name>.minThreadstest.useAtomics现在是test.poolOptions.<pool-name>.useAtomicstest.poolMatchGlobs.child_process现在是test.poolMatchGlobs.forkstest.poolMatchGlobs.experimentalVmThreads现在是test.poolMatchGlobs.vmThreads
{
scripts: {
- "test": "vitest --no-threads"
// For identical behaviour:
+ "test": "vitest --pool forks --poolOptions.forks.singleFork"
// Or multi parallel forks:
+ "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 设计了与 Jest 兼容的 API,旨在简化从 Jest 的迁移。尽管付出了这些努力,您仍然可能会遇到以下差异:
默认全局变量
Jest 默认启用其全局 API。Vitest 不会。您可以选择通过全局配置设置启用全局变量,或者更新您的代码以使用从 vitest 模块导入。
如果您决定禁用全局变量,请注意像 testing-library 这样的常用库将不会自动进行 DOM 清理。
spy.mockReset
Jest 的mockReset会将模拟实现替换为返回 undefined 的空函数。
Vitest 的 mockReset 将模拟实现重置为其原始实现。 也就是说,重置由 vi.fn(impl) 创建的模拟会将模拟实现重置为 impl。
模块模拟
在 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默认会这样处理:当模拟一个模块并希望此模拟扩展到使用相同模块的其他外部库时,您应该明确指出希望模拟哪个第三方库,以便外部库成为您的源代码的一部分,通过使用 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)。如果您依赖它,请不要忘记重命名。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) => {
it('should work', () => new Promise(done => {
// ...
done()
})
})) 钩子
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特有功能,但若曾使用带vue-cli预设的Jest,需安装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);否则您的快照将包含大量转义的引号 (") 字符。