两条街的转角,开着一家老牌饭店和一家新派餐厅。老牌饭店叫 J 记,经营了十几年,菜单厚得能当书读,熟客都说它稳;新派餐厅叫 V 家,背靠最新供应链体系,食材从产地直送,点完菜十分钟就能上桌。你是一名天天要给代码“试吃”的开发者,而这两家餐厅,就是前端测试界最出名的两个“后厨”——Jest 和 Vitest。本文会带你参观两家的后厨:从备菜(安装配置)、点菜(API 写法)、拦截供应商(Mock/Spy)、拍照存档(快照)、试菜完整度(覆盖率)到搬家成本(迁移),一次看清该请哪家厨房帮你把关代码质量。

岗位关联

给前端项目搭测试,是 AI 应用工程质量的地基。学会对比 Jest 与 Vitest,你就能在团队里拍板“测试框架选谁”,避免项目后期推倒重来。

目录
  1. 可视化演示
  2. 框架概览与选型
  3. 安装与配置
  4. 测试API写法
  5. Mock与Spy能力
  6. 快照与覆盖率
  7. ESM与Vite集成
  8. 迁移成本与步骤
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示快速排序以末尾元素为基准的分区过程,小于基准的元素左移,大于基准的元素右移。

本节配有交互动效(进入视口后自动加载,约 3KB)。

快速排序 — 初始状态 数组共9个元素,选择末尾元素6作为基准 pivot=arr[8]=6 即将开始分区操作

框架概览与选型

街角开着两家餐厅。J 记是经营十几年的老牌饭店,菜单厚得能当书读,熟客认的就是这份稳定;V 家是今年新开的现代餐厅,背靠最新供应链,食材从产地直送,点完菜十分钟就能上桌。你要为项目选一个“测试后厨”,就得先把两家餐厅的门面看个明白。

现实世界里,Jest 是 Facebook 于 2014 年开源的老牌测试框架,社区庞大、插件齐全,几乎所有前端项目都见过它;Vitest 则是 2021 年底由 Vite 团队孵化的新秀,主打与 Vite 共享构建管道,把“快”写进了基因。两者都能完成单元测试、组件测试和快照测试,但设计理念分道扬镳。

选型主要看四个维度:生态完整度、运行性能、与现有技术栈的兼容度、迁移成本。老项目已深度依赖 jest-dom、eslint-plugin-jest 等周边库时,留在 Jest 更稳;新项目若底层是 Vite,上 Vitest 几乎零配置接入。一句话:项目用 Vite 就选 Vitest,存量 Jest 生态别硬拆。

所以选餐厅要看老板自己的供应链。食材已标准化直供(Vite 项目),V 家后厨天然合拍;J 记老后厨运营多年、伙计顺手,强行搬家反而折腾。用上面的评分公式给两家打打分,别只看门面,理性决策。

Score=0.4×E生态+0.3×P性能+0.2×C兼容+0.1×M迁移\text{Score} = 0.4 \times E_{\text{生态}} + 0.3 \times P_{\text{性能}} + 0.2 \times C_{\text{兼容}} + 0.1 \times M_{\text{迁移}}
选型评分就是把“生态、性能、兼容、迁移”四个维度按 4:3:2:1 加权求和,权重越大的维度对最终决定影响越大。
  1. 生态权重 0.4表示插件、社区教程、周边工具在选型中的分量,Jest 在这项明显占优。
    E生态=周边工具与社区规模E_{\text{生态}} = \text{周边工具与社区规模}
  2. 性能权重 0.3表示测试运行速度的权重,Vitest 凭借原生 ESM 在这项领先。
    P性能=启动时间的倒数+单测耗时的倒数P_{\text{性能}} = \text{启动时间的倒数} + \text{单测耗时的倒数}
  3. 兼容权重 0.2表示与现有构建链的协同程度,Vite 项目选 Vitest 此项接近满分。
    C兼容=可直接复用模块数全部模块数C_{\text{兼容}} = \frac{\text{可直接复用模块数}}{\text{全部模块数}}
  4. 迁移权重 0.1表示从现有框架切换的成本,权重最低,因为两框架 API 大多通用。
    M迁移=11+需要改动的文件数M_{\text{迁移}} = \frac{1}{1 + \text{需要改动的文件数}}

安装与配置

决定了去哪家后厨,下一步就是置办家伙。J 记的招聘启事写着:老伙计请备好“装修队”——因为它的中央厨房需要自己搭传送带,安装 jest 的同时还得配 babel-jest、@types/jest 一整套;V 家的启事很简单:只要你的项目是 Vite 体系,装一个 vitest 包就能开工。

实际安装上,Jest 通常需要 jest、babel-jest、@types/jest 三个包,处理 TypeScript 还要加 ts-jest;Vitest 只需要 vitest 一个包,若项目已有 vite.config,在 test 字段里补充环境即可。依赖数量差三倍,这也是很多新项目直接选 Vitest 的原因。

配置文件的差异同样明显:Jest 用 jest.config.js 声明 transform、testEnvironment、setupFilesAfterEnv;Vitest 复用 vite.config.ts,在 defineConfig 里追加 test: { environment: 'jsdom', globals: true }。这意味着 Vitest 少维护一套配置体系,开发构建与测试共用同一份模块解析规则,配置漂移的概率大幅降低。

所以 V 家能比 J 家早开火,关键不在炒菜(跑断言)快,而在省掉了排队过传送带(模块转换)的时间。老后厨的传送带是历史包袱,新后厨直接共享 Vite 的流水线,自然更轻快。

Tstart=Tconfig+Ttransform×Nmodules+TrunT_{\text{start}} = T_{\text{config}} + T_{\text{transform}} \times N_{\text{modules}} + T_{\text{run}}
启动耗时等于“配置加载 + 每个模块的转换耗时 × 模块数 + 用例执行耗时”。Jest 和 Vitest 的差距主要藏在中间的转换项。
  1. 配置加载时间框架读取配置文件并加载依赖脚本的固定开销,两者差别不大。
    Tconfig=解析配置与加载脚本的时间T_{\text{config}} = \text{解析配置与加载脚本的时间}
  2. 模块转换总耗时每个源码模块都要被转换工具处理,Jest 每个模块都过 Babel,Vitest 只转必要部分。
    Ttransform×Nmodules=平均转换耗时×模块总数T_{\text{transform}} \times N_{\text{modules}} = \text{平均转换耗时} \times \text{模块总数}
  3. 用例执行总耗时真正的断言执行时间,两个框架在此项差异不大。
    Trun=∑每个测试用例的执行耗时T_{\text{run}} = \sum \text{每个测试用例的执行耗时}

测试API写法

进了后厨,点菜方式大同小异。J 记和 V 家的菜单长得几乎一模一样:describe 是“菜单分类”,it 或 test 是“一道菜”,expect 是“夹一筷子尝味道”。在 J 记点过菜的客人,到 V 家看菜单毫不陌生,这份熟悉感正是两框架 API 高度同源的体现。

两者都提供 describe/it/test、beforeEach/beforeAll、expect 加各种 matcher。核心断言写法完全一致:expect(actual).toBe(expected)、toHaveLength、toContain 都通用。唯一明显差别:Vitest 默认不开启全局 API,需在配置里设 globals: true,否则要显式 import { describe, it, expect } from 'vitest'。

参数化测试两者都有:Jest 用 test.each,Vitest 用 it.each,语法几乎相同;跳过用例也都是 xdescribe、xit 或 describe.skip、it.skip。生命周期钩子上,Jest 有 beforeAll/afterEach,Vitest 完全对齐,还额外支持 onTestFailed 这类更精细的钩子,方便在失败瞬间做现场采集。

两家的“点菜单”高度兼容,你在 J 记学会的写法,到 V 家基本无缝上手。这也解释了为什么迁移成本低:改了厨房,没改菜谱。

Suite={it1,it2,…,itn},expect(actual).matcher(expected)\text{Suite} = \{ \text{it}_1, \text{it}_2, \ldots, \text{it}_n \},\quad \text{expect}(\text{actual}).\text{matcher}(\text{expected})
一个测试套件(describe)就是一组用例(it)的集合,每条用例内部通过“expect 实际值 + 匹配器”来判定结果。
  1. describe 定义套件把相关用例归档到一组,等价于给菜单做分类。
    describe(分类名)\text{describe}(\text{分类名})
  2. it 定义单条用例一个 it 块就是一道待验证的菜,包含名字和执行函数。
    it(用例名,fn)\text{it}(\text{用例名}, \text{fn})
  3. expect 发起断言把实际值交给匹配器,等待比对。
    expect(actual)\text{expect}(\text{actual})
  4. matcher 判定结果匹配器返回通过或失败,常见有 toBe、toHaveLength、toContain。
    matcher(expected)∈{toBe,toHaveLength,toContain,…}\text{matcher}(\text{expected}) \in \{ \text{toBe}, \text{toHaveLength}, \text{toContain}, \ldots \}

Mock与Spy能力

后厨里最难伺候的是“外部供应商”。测一道菜时不想真的给供应商打电话订食材(避免网络请求、真实支付),就得用“模拟食材”——这就是 Mock。而 Spy 则像在旁边装了个摄像头,盯着某个厨师有没有真的被叫去干活,而不替换他的手艺。

Jest 里用 jest.fn() 创建模拟函数,jest.spyOn(obj, 'method') 观察对象方法;Vitest 对应的是 vi.fn() 和 vi.spyOn()。两者返回值都带 calls 与 mock 属性,记录调用次数、入参和返回值。对断言来说,toHaveBeenCalledTimes 与 toHaveBeenCalledWith 在两框架里写法完全一致。

模块级拦截是另一个核心能力:jest.mock('./api') 可以把整个模块替换成假实现,vi.mock 作用相同。Fake Timers 也双雄必备:Jest 用 jest.useFakeTimers(),Vitest 用 vi.useFakeTimers(),用于测 setTimeout、轮询等时序逻辑。两者在 mock 体系上的对应关系几乎是逐行的。

模拟食材再顺手,也不能每道菜都全用假货。真正要拦住的是“外部供应商”(网络、数据库、第三方 SDK),自己的切菜逻辑该真刀真枪练。V 家与 J 家在这点上思路一致,只是把 jest 前缀换成了 vi。

∣calls(mockFn)∣=n⇔expect(mockFn).toHaveBeenCalledTimes(n)\left| \text{calls}(\text{mockFn}) \right| = n \quad \Leftrightarrow \quad \text{expect}(\text{mockFn}).\text{toHaveBeenCalledTimes}(n)
模拟函数每次被调用都会记入一个“账本”,账本条数等于调用次数,断言就是在核对账本条数是否等于期望值。
  1. mockFn 记录每次调用每调用一次模拟函数,就把本次参数追加进 calls 列表。
    calls(mockFn)=[args1,args2,…]\text{calls}(\text{mockFn}) = [\text{args}_1, \text{args}_2, \ldots]
  2. calls 数组长度数组长度即为总调用次数。
    n=calls(mockFn).lengthn = \text{calls}(\text{mockFn}).\text{length}
  3. 断言比对把记录次数与期望次数比较,相等则通过。
    expect(mockFn).toHaveBeenCalledTimes(n)  ⟺  ∣calls∣=n\text{expect}(\text{mockFn}).\text{toHaveBeenCalledTimes}(n) \iff |\text{calls}| = n

快照与覆盖率

餐厅要保证招牌菜每次端出来都长一样,最好的办法是第一次做好时拍一张“标准照”存档,以后每次出餐都跟照片比对。这就是快照测试:第一次运行时把组件输出序列化存入 snapshots 目录,之后每次运行都拿新渲染结果与存档比对,不一致就报错。

Jest 与 Vitest 都内置快照测试,语法一致:expect(component).toMatchSnapshot()。差异在细节:Vitest 用 prettier 格式化快照文件,Jest 则内置自己的格式化逻辑;更新快照都是 -u 或 --update 参数,但 Vitest 对渲染差异的“染色”体验更友好,一眼能看出哪里变了。

覆盖率方面,Jest 内置基于 Istanbul 的 coverage 统计,Vitest 默认用 v8 收集器(也可切回 Istanbul)。两者都能输出 lines、branches、functions、statements 四类指标,并支持 thresholds 配置——覆盖率低于门槛,CI 直接失败。

快照像菜品的“标准照”,覆盖率高则像试菜员走遍了菜单的每一道菜。但要警惕:100% 覆盖率不代表菜好吃,只代表每道菜都有人尝过一口;快照也别乱更新,照片本身拍错了,后续比对就全失真。

Coverage=LcoveredLtotal×100%\text{Coverage} = \frac{L_{\text{covered}}}{L_{\text{total}}} \times 100\%
覆盖率是被测试运行真正执行到的代码行数,占被测代码总行数的百分比。它衡量“哪些代码被跑到”,不衡量“跑得对不对”。
  1. 已覆盖行数测试运行过程中真正被执行命中的代码行数。
    Lcovered=∑执行命中的行L_{\text{covered}} = \sum \text{执行命中的行}
  2. 总行数被测文件里所有有效代码行,不含空行与注释。
    Ltotal=∑文件有效行L_{\text{total}} = \sum \text{文件有效行}
  3. 归一化百分比两数相除后放大到百分制,作为质量健康指标。
    LcoveredLtotal×100%∈[0%,100%]\frac{L_{\text{covered}}}{L_{\text{total}}} \times 100\% \in [0\%, 100\%]

ESM与Vite集成

V 家餐厅最大的秘密武器,是它的食材供应链直接对接“标准产区”——ESM 原生模块。J 记虽然也能拿到标准食材,但后厨规定所有食材必须过一遍自家预处理线(转成 CommonJS 格式),凭空多了一道工序,出餐自然慢。

技术本质在于运行机制:Jest 基于 Node 的 CJS 体系加 Babel 转换,遇到 import 语法要先由 babel-jest 或 ts-jest 转成 require;对原生 .mjs 文件的支持至今仍是实验性状态,需要 --experimental-vm-modules 标志。Vitest 底层直接复用 Vite 的模块解析与 esbuild 转换,import 语句原生可跑,不需要额外的语法降级。

对 Vite 项目而言,Vitest 可以直接复用 vite.config 里的 resolve.alias 和 plugins(如 Vue 插件、React 插件),测试环境与应用环境共享同一套模块规则;Jest 则需要单独维护 transform、moduleNameMapper,配置漂移是常见痛点。这也是 Vite 系项目里 Vitest 集成“顺滑得像自家厨房”的根本原因。

V 家的快不是玄学——省掉的正是 J 家每次开工前给食材过预处理线的功夫。标准食材(ESM 模块)直接下锅,多出来的时间全花在真正的出菜(执行断言)上。

Speedup=TJestTVitest≈Tbabel×N+TrunTesbuild×N+Trun\text{Speedup} = \frac{T_{\text{Jest}}}{T_{\text{Vitest}}} \approx \frac{T_{\text{babel}} \times N + T_{\text{run}}}{T_{\text{esbuild}} \times N + T_{\text{run}}}
加速比就是两框架总耗时的比值。当模块数 N 越来越大时,分子分母里的转换耗时差距被放大,Vitest 的优势更明显。
  1. Jest 总耗时每个模块都经过 Babel 转换,再执行测试。
    TJest=Tbabel×N+TrunT_{\text{Jest}} = T_{\text{babel}} \times N + T_{\text{run}}
  2. Vitest 总耗时esbuild 预处理极快且按需转换,执行占比更高。
    TVitest=Tesbuild×N+TrunT_{\text{Vitest}} = T_{\text{esbuild}} \times N + T_{\text{run}}
  3. 加速比公式两式相除,模块数 N 越大,比值越接近两者的转换速度之比。
    Speedup≈TbabelTesbuild\text{Speedup} \approx \frac{T_{\text{babel}}}{T_{\text{esbuild}}}

迁移成本与步骤

如果老板最终决定从 J 记后厨搬进 V 家,最关心的是“搬家费多少、要不要重新装修”。好消息是:两家的菜单几乎一样,绝大多数菜谱(测试用例)可以原封不动,真正要换的是后厨管道(配置文件)和少数工具代号(jest 前缀)。

迁移分三步走:第一步迁移配置文件,把 jest.config.js 里的 testEnvironment、setupFiles 映射到 vite.config.ts 的 test 字段,同时把 npm script 里的 jest 换成 vitest;第二步全局替换 API 前缀,把 jest.fn、jest.mock、jest.spyOn 等替换成 vi 前缀,纯文本替换即可;第三步处理边界差异,比如 Jest 的 moduleNameMapper 对应 Vitest 的 resolve.alias,快照格式变动需要重新生成一次快照。

成本可以从两个维度量化:改动文件的百分比(通常只有 10%-20% 的测试文件需要动手,其余 import 与断言写法不变)与 CI 集成的改动量(GitHub Actions 里把 npx jest 换成 npx vitest run)。如果项目深度依赖 jest 生态插件(如 jest-sonar-reporter),还要找替代品或做额外适配。

搬家总结:厨房(构建体系)换了,菜谱(测试逻辑)大多没动。预算充足、团队熟悉 Vite,这笔搬家费通常一周内回本;如果团队已在 Jest 上沉淀大量插件与习惯,就让 J 记继续好好营业,别为跟风搬家。

Cost=wcfg⋅configFiles+wapi⋅jestPrefixCount+wedge⋅snapshotDirs\text{Cost} = w_{\text{cfg}} \cdot \text{configFiles} + w_{\text{api}} \cdot \text{jestPrefixCount} + w_{\text{edge}} \cdot \text{snapshotDirs}
迁移总成本 = 配置重写权重 × 配置文件数 + API 替换权重 × jest 前缀出现次数 + 边界处理权重 × 快照目录数。权重可根据团队熟练度调整。
  1. 配置重写项jest.config.js 转成 vite.config.ts 中的 test 块,是权重最高的一步。
    wcfg⋅configFilesw_{\text{cfg}} \cdot \text{configFiles}
  2. API 替换项jest.xxx 前缀替换为 vi.xxx,用带权重的出现次数表示工作量。
    wapi⋅jestPrefixCountw_{\text{api}} \cdot \text{jestPrefixCount}
  3. 边界差异项快照目录重新生成与 jest 专属插件替换带来的额外成本。
    wedge⋅snapshotDirsw_{\text{edge}} \cdot \text{snapshotDirs}

AI 应用场景

Vite 驱动的中后台项目测试底座

阶段:工程化基建

解决的问题:传统工具链里测试与构建各维护一套转换规则,容易出现“开发能跑、测试报错”的割裂。Vitest 原生支持 ESM,让动态 import 加载模型配置这类 AI 产品常见场景在测试环境下表现一致,调试体验远优于 Jest。

具体案例:一个 AI 对话产品的管理后台,组件按需加载提示词模板(import() 动态导入)。团队在 Vite 项目里接入 Vitest,300 个测试文件的启动耗时从 Jest 的 4.2 秒降到 1.1 秒,CI 单测阶段整体缩短 3 分钟。

存量 Jest 项目的渐进式迁移

阶段:技术债治理

解决的问题:老项目深度使用 Jest,直接推翻重来风险高。渐进式迁移让新旧测试并行运行,每迁移一个模块立刻获得速度红利,同时用脚本批量替换 jest 前缀,降低人工出错率。

具体案例:某可视化平台把 1200 个测试文件按模块分批迁移,先迁纯函数与工具库(改动最小、收益最大),再处理组件级快照。配合 API 映射脚本,两个月完成 80% 迁移,CI 时间减半。

组件库质量门槛

阶段:质量保障

解决的问题:组件库需要“每道菜都有标准照”——快照测试防止 UI 意外变动;覆盖率阈值把核心组件锁死在合理水位,防止回归悄悄溜进发布流程。

具体案例:AI 应用里的图表组件库(Vue3 + Vite)配 Vitest 与 @vue/test-utils,每个组件配套快照与交互测试;发布前执行 vitest run 并配覆盖率阈值 lines >= 80,低于门槛直接阻断发布流水线。

Python 演示

python50 行
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""A tiny converter from Jest test files to Vitest test files.""" import sys # Mapping from Jest API to Vitest APIAPI_MAP = {    "jest.fn(": "vi.fn(",    "jest.mock(": "vi.mock(",    "jest.spyOn(": "vi.spyOn(",    "jest.useFakeTimers(": "vi.useFakeTimers(",    "jest.clearAllMocks(": "vi.clearAllMocks(",    "jest.resetModules(": "vi.resetModules(",}  def convert_line(line):    """把一行 Jest 代码替换成对应的 Vitest 写法"""    for jest_api, vitest_api in API_MAP.items():        if jest_api in line:            return line.replace(jest_api, vitest_api, 1)    if "from '@jest/globals'" in line:        return line.replace("@jest/globals", "vitest").replace("jest", "vi")    return line  def convert_source(source):    """逐行转换完整的测试文件源码"""    lines = source.splitlines()    return "\n".join(convert_line(line) for line in lines)  def main():    if len(sys.argv) < 2:        print("Usage: python jest_to_vitest.py <test-file>")        return 1    file_path = sys.argv[1]    try:        with open(file_path, "r", encoding="utf-8") as f:            source = f.read()    except OSError as exc:        print("Failed to read file:", exc)        return 1    print(convert_source(source))    return 0  if __name__ == "__main__":    sys.exit(main())
目的:一条命令把 Jest 测试文件里的 jest.xxx 批量替换成 vi.xxx,帮你快速盘点迁移成本并完成 80% 的机械改造。
  1. API_MAP = { "jest.fn(": "vi.fn(", ... }建立“旧 API → 新 API”的映射表,相当于搬家清单。
  2. if jest_api in line:检查当前行是否包含待替换的旧 API。
  3. return line.replace(jest_api, vitest_api, 1)只替换一次并立即返回,避免同一行被反复处理。
  4. if "from '@jest/globals'" in line:处理从 @jest/globals 显式导入 jest 的写法,换成从 vitest 导入 vi。
  5. lines = source.splitlines()按行拆分源码,逐行转换。
  6. return "\n".join(convert_line(line) for line in lines)转换完成后再用换行符拼回源码。

常见误区

误区我一直以为 Vitest 是 Vite 的附属插件,只有项目用了 Vite 才能上 Vitest,所以老项目碰都不敢碰。

正解Vitest 打包自带了 Vite 引擎,任何 Node 项目都能直接运行,只是普通项目需要额外写 alias、plugin 等适配;而 Vite 项目能省掉这些配置,体验最顺。就像 V 家餐厅自带全套厨具,哪怕你的店不在它家供应链上也能开业,只是接入它家体系更省事。

为何常见:名字太像、官方文档首页全是 Vite 项目演示,很容易让人误以为两者是硬绑定关系。

误区听说 Jest 不支持 ESM,我们的项目全用 import 语法,所以只能选 Vitest。

正解Jest 通过 babel-jest 或 ts-jest 把 ESM 语法转成 CJS 运行,绝大多数场景都工作得很好;只是对原生 .mjs 文件与实验性 ESM 支持仍有限制,需要 --experimental-vm-modules 标志。准确说法是:Jest 能“处理”ESM 语法,但不是“原生运行”ESM,速度和边界体验不如 Vitest。

为何常见:社区里“Jest ESM 支持是实验性”的说法被简化成“不支持”,以讹传讹。

误区一想到迁移就要改几百个测试文件,工作量太大,干脆继续用 Jest 吧。

正解绝大多数测试文件只用 describe/it/expect 这套通用 API,迁移时原样保留;真正要改的是 jest.xxx 前缀(改成 vi.xxx)和配置文件。典型组件库项目改动文件占比通常在 10%-20%,用本文的转换脚本跑一遍就能摸底。

为何常见:把“换框架”类比成“换语言”,高估了 API 差异;实际上两者 API 高度同源,迁移是改管道而非改菜谱。

误区CI 里覆盖率必须 100%,不然就是测试写得不够努力,团队该加班补用例。

正解覆盖率只衡量“哪些代码被跑到”,不衡量“跑得对不对”。为了凑 100% 写一堆不痛不痒的断言,反而降低测试可维护性。更合理的做法:核心业务模块设 80% 门槛,工具函数可放宽到 60%,把精力放在关键路径的行为验证上。

为何常见:覆盖率是最直观的“安全数字”,管理者容易拿它当 KPI,团队也会为了数字好看而堆测试。

面试题

Q1. Vitest 这个测试框架,主要是基于哪个构建工具打造的?

单选题 · easy

  • A.基于 Babel 编译器
  • B.基于 Vite 构建器← 正确答案
  • C.基于 webpack 打包
  • D.基于 Rollup 打包

答案:B。Vitest 由 Vite 团队打造,底层复用 Vite 的模块解析与转换管道,所以选 B。A 的 Babel 是 Jest 转换链路的底层,C 和 D 是其他打包器,都不是 Vitest 的构建基础。

Q2. 在 Vitest 中,原先 jest.fn() 创建模拟函数的写法应改成哪种?

单选题 · easy

  • A.mock.fn() 调用形式
  • B.vi.fn() 调用形式← 正确答案
  • C.spy.fn() 调用形式
  • D.fake.fn() 调用形式

答案:B。Vitest 用 vi 前缀统一管理 mock、spy、fake timer 等测试工具,jest.fn() 对应 vi.fn(),jest.mock() 对应 vi.mock()。就像搬家后工具改了名字,用途完全一样。

Q3. 测试覆盖率公式 Coverage = L_covered / L_total 中的 L_total 表示什么?

单选题 · easy

  • A.全部代码文件的数量
  • B.测试文件的总行数
  • C.被测代码总行数← 正确答案
  • D.通过用例的数量

答案:C。L_total 是被测代码的总有效行数,L_covered 是测试执行命中的行数。把测试当试菜,L_total 是菜单上所有菜,L_covered 是真正被尝过的菜,两者相除才是覆盖率。

Q4. 在 Vitest 中,想观察某个已有对象的方法是否被调用,应该使用哪个 API?

单选题 · mid

  • A.vi.fn() 创建假函数
  • B.vi.mock() 拦截整个模块
  • C.vi.spyOn() 观察指定方法← 正确答案
  • D.vi.useFakeTimers() 伪造计时

答案:C。vi.spyOn() 只“观察”已有对象的方法而不替换实现,适合验证某函数是否被调用,相当于装摄像头。vi.fn() 是造新假函数,vi.mock() 是替换整个模块,vi.useFakeTimers() 是伪造时间。

Q5. 快照测试第一次运行时会发生什么?

单选题 · mid

  • A.直接失败并删除快照
  • B.生成快照文件保存基线← 正确答案
  • C.跳过当前所有断言
  • D.强制要求人工审核通过

答案:B。首次运行没有可比对的基线,框架会生成快照文件并保存,后续运行才拿新输出与基线比对。这相当于第一次给招牌菜拍照存档,之后每次出餐对照照片查差异。

Q6. 关于 Jest 与 Vitest 的对比,下列哪些说法是正确的?

多选题 · mid

  • A.Jest 社区生态更成熟一些← 正确答案
  • B.Vitest 原生支持 ESM 模块← 正确答案
  • C.Vitest 必须依赖 Babel
  • D.Jest 启动速度明显更快

答案:AB。A 正确,Jest 生态的插件、教程和周边库更全;B 正确,Vitest 复用 Vite 的 esbuild 转换,原生支持 ESM。C 错在 Vitest 不依赖 Babel,D 错在 Jest 的启动速度反而不占优势。

Q7. 为什么 Jest 处理 ESM 模块需要额外的配置?

单选题 · hard

  • A.因为 Jest 只能运行于浏览器
  • B.因为底层默认走 CJS 转换← 正确答案
  • C.因为工具无法识别 import
  • D.因为 ESM 规范尚未发布

答案:B。Jest 基于 CJS 生态构建,默认通过 Babel 把 import 转成 require 才能运行,所以需要额外配置。A 错在 Jest 跑在 Node 环境;C 错在 Babel 完全能解析 import 语法;D 错在 ESM 早已成为标准规范。

Q8. 从 Jest 迁移到 Vitest,通常需要完成哪些工作?

多选题 · hard

  • A.把 jest. 前缀替换为 vi.← 正确答案
  • B.重写全部断言逻辑
  • C.将配置迁移到 vite.config← 正确答案
  • D.更新 npm 脚本的执行命令← 正确答案

答案:ACD。迁移需要替换 jest.xxx 前缀为 vi.xxx、把 jest.config.js 的字段映射进 vite.config.ts 的 test 块、把 npx jest 换成 npx vitest run。B 不需要,因为 describe/it/expect 等断言逻辑两框架完全兼容。

Q9. Vitest 默认使用的覆盖率收集器是哪一个?

单选题 · hard

  • A.官方自研的 v8 引擎工具← 正确答案
  • B.Babel 自带的转换工具
  • C.Webpack 内置的分析器
  • D.ESLint 的规则解析器

答案:A。Vitest 默认使用 v8 作为覆盖率收集器(也可配置为 Istanbul),两者都能输出 lines、branches、functions、statements 四类指标。Babel、Webpack、ESLint 都不是覆盖率统计工具。

Q10. Vitest 在非 Vite 项目中也能正常使用,原因包括哪些?

多选题 · extreme

  • A.打包自带了默认构建引擎← 正确答案
  • B.无配置时会生成一份默认配置← 正确答案
  • C.必须手写完整 Vite 配置才能跑
  • D.仅能处理纯 JavaScript 文件

答案:AB。Vitest 直接打包了 Vite 引擎,即使项目没有 vite.config,它也会用一份默认配置独立运行,因此非 Vite 项目可用。C 错在不需要完整手写配置,D 错在它同样支持 TypeScript 等文件。