迁移指南
迁移到 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 讨论页面 上阅读我们关于浏览器模式的理念。
大多数更改都是增量的,但也有一些小的破坏性更改:
none
provider 已重命名为preview
#5842preview
provider 现在是默认值 #5842indexScripts
已重命名为orchestratorScripts
#5842
已移除的弃用选项
部分已弃用选项已被移除:
vitest typecheck
命令 - 请改用vitest --typecheck
VITEST_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.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"
// 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);
否则您的快照将包含大量转义的引号 (") 字符。