从 Jest 迁移
Vitest 的 API 兼容 Jest,旨在尽可能简化从 Jest 迁移的过程。即便如此,你仍可能遇到以下差异:
默认情况下的全局 API
Jest 默认启用全局 API,而 Vitest 不会。你可以通过 globals 配置项启用全局 API,或改为从 vitest 模块导入所需 API。
如果选择不启用全局 API,请注意,像 testing-library 这样的常见库将不会自动执行 DOM 清理。
mock.mockReset
Jest 的 mockReset 会将模拟实现替换为空函数,该函数返回 undefined。
Vitest 的 mockReset 会将模拟实现恢复为原始实现。也就是说,重置通过 vi.fn(impl) 创建的模拟函数时,会将模拟实现恢复为 impl。
mock.mock 状态会保持不变
调用 .mockClear 时,Jest 会重新创建模拟状态,因此你始终需要通过 getter 访问它。相反,Vitest 会一直保留该状态的引用,因此你可以重复使用它:
const mock = vi.fn()
const state = mock.mock
mock.mockClear()
expect(state).toBe(mock.mock) // fails in Jest模块模拟
在 Jest 中模拟模块时,工厂函数的返回值会作为默认导出。在 Vitest 中,工厂函数必须返回一个对象,并在其中显式定义每个导出。例如,以下 jest.mock 需要改为:
jest.mock('./some-path', () => 'hello')
vi.mock('./some-path', () => ({
default: 'hello',
})) 更多详情请参阅 vi.mock API 部分。
自动模拟行为
与 Jest 不同,除非调用 vi.mock(),否则不会加载 <root>/__mocks__ 中的模拟模块。如果希望像 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}`testNamePattern(-t 标志)也是如此:Vitest 会匹配以 > 拼接的完整名称,而 Jest 会匹配以空格拼接的名称。请相应更新跨套件和测试名称的匹配模式,也可以只匹配一个片段(-t adds),或在片段之间使用通配符(-t 'math.*adds')。
- vitest -t 'math adds'
+ vitest -t 'math > adds'环境变量
与 Jest 一样,如果尚未设置 NODE_ENV,Vitest 会将其设为 test。Vitest 还提供了对应于 JEST_WORKER_ID 的 VITEST_POOL_ID(始终小于或等于 maxWorkers);如果你的代码依赖该变量,请记得更改名称。Vitest 还提供 VITEST_WORKER_ID,这是当前 worker 的唯一 ID。该数字不受 maxWorkers 影响,并会随每个新建的 worker 递增。
替换对象属性
在 Jest 中,如果要修改对象,可以使用 replaceProperty API。在 Vitest 中,可以使用 vi.stubEnv 或 vi.spyOn 实现相同效果。
Done 回调
Vitest 不支持使用回调函数声明测试。你可以将其改写为 async/await 函数,或使用 Promise 模拟回调形式。
it('should work', (done) => {
it('should work', () => new Promise(done => {
// ...
done()
})
})) 钩子
在 Vitest 中,beforeAll/beforeEach 钩子可以返回清理函数。因此,如果你的钩子返回的值既不是 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 preset 使用 Jest,需要安装 jest-serializer-vue 包,并在 snapshotSerializers 中指定它:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
snapshotSerializers: ['jest-serializer-vue']
}
})否则,快照中会包含许多转义后的 " 字符。
自定义快照匹配器 experimental 4.1.3+
Jest 从 jest-snapshot 导入快照工具。在 Vitest 中,请改用从 vitest 导入的 Snapshots:
const { toMatchSnapshot } = require('jest-snapshot')
import { Snapshots } from 'vitest'
const { toMatchSnapshot } = Snapshots
expect.extend({
toMatchTrimmedSnapshot(received: string, length: number) {
return toMatchSnapshot.call(this, received.slice(0, length))
},
})内联快照也同样如此:
const { toMatchInlineSnapshot } = require('jest-snapshot')
import { Snapshots } from 'vitest'
const { toMatchInlineSnapshot } = Snapshots
expect.extend({
toMatchTrimmedInlineSnapshot(received: string, inlineSnapshot?: string) {
return toMatchInlineSnapshot.call(this, received.slice(0, 10), inlineSnapshot)
},
})完整指南请参阅自定义快照匹配器。