前端 Jest 单元测试零基础实战:模板、提效、避坑、面试题(Vue 项目可用)
一、前言
做前端开发多年,很多小伙伴和我一样,日常业务开发几乎很少写单元测试,但面试、大型项目、公共组件库开发又绕不开 Jest。本文从零基础入门出发,结合多年实战经验,讲解 Jest 基础用法、项目落地模板、提效技巧、开发避坑要点,最后附上高频面试题,看完既能上手干活,也能应对面试,适配普通 JS 项目、Vue 项目。
核心结论:单元测试不是所有业务都要写,纯工具函数、公共组件、核心校验/计算逻辑优先覆盖,普通页面视图逻辑可选择性忽略,平衡质量与开发效率。
二、Jest 基础认知
2.1 Jest 是什么
Jest 是 Meta 推出的 JavaScript 自动化测试框架,开箱即用,内置断言、模拟、覆盖率统计等能力,是目前前端最主流的单元测试工具,完美适配 Vue、React、原生 JS/TS 项目。
2.2 Jest 和 console.log 的区别
很多新手会混淆两者,这里做清晰区分:
| 工具 | 核心作用 | 执行方式 | 适用场景 |
|---|---|---|---|
| console.log | 打印日志,人工肉眼查看结果 | 手动调试,逐行查看 | 开发阶段临时排查问题 |
| Jest | 自动化断言,机器自动校验代码对错 | 批量运行、自动判断结果 | 代码回归防护、自动化质量校验 |
简单理解:console.log 是自己检查代码,Jest 是自动化批改工具。二者互补,互不替代。
2.3 核心优势
- 零复杂配置,安装即可使用;
- 内置
jsdom,可模拟浏览器环境,支持 DOM 相关测试; - 一键生成代码覆盖率报告,直观查看未测试代码;
- 支持异步代码、定时器、接口 Mock,适配前端各类场景;
- 支持文件监听,修改代码自动重跑测试。
三、快速搭建环境(通用 + Vue 项目)
3.1 通用 JS 项目搭建
- 项目初始化
bash
npm init -y
- 安装 Jest 开发依赖
bash
npm install --save-dev jest
- 配置运行命令(
package.json)
json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage"
}
}
npm run test:单次执行所有测试用例npm run test:watch:监听文件变化,自动重跑测试(开发常用)npm run test:cov:生成代码覆盖率报告
3.2 Vue 项目专属配置
适配 Vue2 / Vue3,支持解析 .vue 单文件组件:
- 安装全套依赖
bash
# Vue3 项目
npm install --save-dev jest @vue/test-utils @vue/vue3-jest babel-jest @babel/core @babel/preset-env
# Vue2 项目
npm install --save-dev jest @vue/test-utils vue-jest babel-jest @babel/core @babel/preset-env
- 根目录新建
jest.config.js
js
// Vue3 配置
module.exports = {
transform: {
"^.+\\.vue$": "@vue/vue3-jest",
"^.+\\.js$": "babel-jest"
},
testMatch: ["**/*.test.js"],
moduleFileExtensions: ["vue", "js"],
testEnvironment: "jsdom"
};
- 根目录新建
babel.config.json
json
{
"presets": [
["@babel/preset-env", { "targets": { "node": "current" } }]
]
}
- 沿用上面
package.json的 test 命令即可。
四、核心语法与万能测试模板(直接复制使用)
4.1 三大基础语法
test/it:定义单个测试用例,两个 API 完全等价;expect:断言核心函数,用来校验执行结果;- 匹配器:定义预期规则(等于、包含、真假、大小比较等)。
4.2 高频常用匹配器(90% 场景够用)
js
// 基础类型等值判断
expect(10).toBe(10)
expect(null).toBeNull()
expect(undefined).toBeUndefined()
// 引用类型(数组/对象)判断
expect([1,2]).toEqual([1,2])
expect({name: "test"}).toEqual({name: "test"})
// 布尔判断
expect(1).toBeTruthy()
expect(0).toBeFalsy()
// 字符串/数组包含
expect("hello").toContain("ell")
// 数值大小比较
expect(10).toBeGreaterThan(5)
expect(3).toBeLessThan(8)
// 取反
expect(5).not.toBe(10)
4.3 分组语法 describe
多个关联用例分组管理,结构更清晰:
js
describe("加法函数测试", () => {
test("1 + 1 = 2", () => {
expect(1 + 1).toBe(2)
})
})
4.4 生命周期钩子(复用前置逻辑)
多个用例需要重复执行代码时使用:
beforeEach:每个用例执行前触发afterEach:每个用例执行后触发beforeAll:所有用例执行前只触发一次afterAll:所有用例执行后只触发一次
4.5 万能通用模板(纯函数专用,提效核心)
使用规则:直接引用已写好的业务代码,不复制、不修改原代码,仅补充测试逻辑。
模板代码(.test.js 文件)
js
// 引入业务代码(直接引用项目原有函数)
import { 你的函数名 } from './目标文件'
// 分组
describe('你的函数名 单元测试', () => {
// 用例1:正常入参场景
test('正常输入,返回预期结果', () => {
const result = 你的函数名(正常参数)
expect(result).toBe(预期结果)
})
// 用例2:边界值场景(0、空字符串、空数组等)
test('边界值输入,返回预期结果', () => {
const result = 你的函数名(边界参数)
expect(result).toBe(预期结果)
})
// 用例3:异常入参场景(非法类型、null、undefined)
test('异常输入,返回预期结果', () => {
const result = 你的函数名(异常参数)
expect(result).toBe(预期结果)
})
})
实战示例
业务代码 utils.js:
js
export function formatPrice(num) {
if (typeof num !== 'number') return '0.00'
return num.toFixed(2)
}
测试代码 utils.test.js:
js
import { formatPrice } from './utils'
describe('formatPrice 单元测试', () => {
test('正常数字,返回格式化价格', () => {
expect(formatPrice(10)).toBe('10.00')
})
test('输入数值0,返回格式化价格', () => {
expect(formatPrice(0)).toBe('0.00')
})
test('输入非数字,返回默认值', () => {
expect(formatPrice('abc')).toBe('0.00')
})
})
4.6 Vue 组件万能测试模板
js
import { mount } from '@vue/test-utils'
import 组件名 from './组件名.vue'
describe('Vue 组件测试', () => {
// 基础渲染测试
test('组件正常挂载渲染', () => {
const wrapper = mount(组件名)
expect(wrapper.exists()).toBe(true)
})
// 文本渲染测试
test('组件展示指定文本', () => {
const wrapper = mount(组件名)
expect(wrapper.text()).toContain('展示内容')
})
// 模拟点击交互
test('点击按钮触发对应逻辑', async () => {
const wrapper = mount(组件名)
await wrapper.find('button').trigger('click')
// 校验交互后的结果
expect(wrapper.find('span').text()).toBe('变更后内容')
})
})
4.7 异步代码模板(接口/定时器)
前端异步场景高频使用,统一使用 async/await 写法:
js
// 模拟异步请求函数
function fetchData() {
return Promise.resolve('success')
}
test('异步请求正常返回数据', async () => {
const res = await fetchData()
expect(res).toBe('success')
})
五、自动生成测试代码(大幅提效)
手动写测试效率低,推荐 3 种自动化生成方案,结合模板使用效率翻倍。
5.1 VSCode AI 插件(日常开发首选)
- GitHub Copilot :选中函数/文件,输入注释
生成 Jest 单元测试,覆盖正常、边界、异常场景,AI 自动生成完整用例; - 腾讯 CodeBuddy :国内免费替代,右键代码 →
Generate tests,一键生成 Jest 测试代码。
技巧:AI 只生成基础骨架,人工补充边界、异常分支即可。
5.2 全局命令行工具(老项目批量补测)
使用 ai-unit-test-generator 批量扫描项目,自动生成全文件测试用例:
bash
# 全局安装
npm i -g ai-unit-test-generator
# 项目根目录执行,批量生成测试
ai-unit-test generate
5.3 VSCode 自定义代码片段(零插件快速生成模板)
- 打开 VSCode → 首选项 → 用户代码片段 → 选择
javascript.json; - 粘贴以下配置,保存;
- 编辑器输入
jesttest+ Tab,自动生成测试模板。
json
{
"Jest 通用测试模板": {
"prefix": "jesttest",
"body": [
"import { $1 } from './${2}'",
"",
"describe('$1 单元测试', () => {",
" test('正常输入,返回预期结果', () => {",
" expect($1($3)).toBe($4)",
" })",
"",
" test('边界值输入,返回预期结果', () => {",
" expect($1($5)).toBe($6)",
" })",
"",
" test('异常输入,返回预期结果', () => {",
" expect($1($7)).toBe($8)",
" })",
"})"
],
"description": "快速生成 Jest 单元测试模板"
}
}
六、开发注意事项 & 避坑指南(实战必看)
6.1 核心原则:哪些代码该测,哪些不用测
✅ 建议编写单元测试
- 通用工具函数、数据格式化、金额/日期处理;
- 表单校验、权限判断等核心纯逻辑;
- 公司内部公共组件库、开源组件;
- 长期维护、多人协作的大型项目核心业务逻辑。
❌ 无需编写单元测试
- 临时活动页、短期迭代页面;
- 纯视图渲染、样式布局、简单 DOM 展示;
- 第三方开源库(axios、UI 组件库等)。
6.2 开发避坑要点
- 不依赖真实接口/外部环境,必须 Mock
测试不能请求真实后端接口、数据库,否则测试不稳定、运行缓慢,使用jest.mock模拟接口、第三方模块。 - 只测对外表现,不测实现细节
错误:校验函数内部变量、私有方法;
正确:只校验函数返回值、组件渲染结果、交互后的页面表现。 - 异步代码强制使用 async/await
不规范的 Promise 写法容易导致用例提前结束,断言失效。 - 测试文件与业务文件同级存放
目录结构:utils.js+utils.test.js,便于查找和维护。 - 不追求 100% 代码覆盖率
核心逻辑覆盖率达到 80% 以上即可,过度追求全量覆盖只会增加维护成本。 - 测试代码也是正式代码,保持规范
命名语义化,一个用例只测试一个逻辑,使用describe分组管理。 - 禁止为了测试改造原有业务代码
业务代码优先适配业务,测试代码适配业务代码,杜绝本末倒置。
6.3 提效技巧总结
- 每个函数固定写 3 个用例:正常、边界、异常,精简不冗余;
- 全程使用模板 + AI 生成,减少重复编码;
- 开发时开启
test:watch监听模式,改代码自动跑测试; - 公共 Mock、公共前置逻辑抽离复用,避免重复代码。
七、Jest 高频面试题(前端面试必考)
7.1 基础概念题
-
说说 Jest 的作用,以及和 console.log 的区别?
答:Jest 是前端自动化单元测试框架,用于自动校验代码逻辑正确性,防止迭代产生回归 Bug;
console.log仅用于打印日志,需要人工肉眼判断结果,属于手动调试工具。Jest 可批量、自动化执行用例,适合质量管控,二者互补使用。 -
什么是断言?Jest 常用的匹配器有哪些?
答:断言就是校验代码执行结果是否符合预期的逻辑。常用匹配器:
toBe、toEqual、toContain、toBeTruthy、toBeFalsy、not取反等。 -
toBe和toEqual的区别?答:
toBe基于严格相等(===) ,适合判断基本数据类型(数字、字符串、null 等);toEqual会递归对比内容,适合数组、对象等引用类型。
7.2 语法与场景题
-
Jest 中的 describe、test、生命周期钩子分别作用是什么?
答:
describe用于分组管理多个关联测试用例;test定义单个测试用例;生命周期钩子(beforeEach/afterEach/beforeAll/afterAll)用于抽离重复执行的前置/后置逻辑。 -
如何测试异步代码?有几种写法?
答:两种主流写法:
- Promise 链式调用,需要
returnPromise; async/await(推荐),写法简洁易维护。
- 为什么测试接口时需要 Mock axios?如何模拟接口请求?
答:真实接口依赖网络、后端服务,会导致测试不稳定、运行慢。使用jest.mock('axios')全局模拟模块,再通过mockResolvedValue自定义返回假数据。
7.3 实战经验题
-
项目中哪些代码会写单元测试,哪些不写?为什么?
答:会对通用工具函数、核心校验逻辑、公共组件编写单元测试,这类代码复用率高、出错影响面大;纯视图页面、短期活动页一般不写,因为需求迭代快、DOM 耦合度高,测试维护成本远大于收益。
-
前端单元测试是否需要追求 100% 覆盖率?
答:不需要。核心业务逻辑保证高覆盖率即可,纯样式、临时逻辑、第三方库无需覆盖,过度追求全量覆盖率会增加大量维护成本,降低开发效率。
-
使用 Jest 测试 Vue 组件时,主要测试哪些内容?
答:主要测试组件是否正常挂载、文本/属性渲染是否正确、点击/输入等交互逻辑是否生效;一般不测试样式、原生 DOM 细节。
7.4 拓展题
- Jest 的
jsdom作用是什么?
答:jsdom是纯 JS 实现的浏览器 DOM 环境,Jest 运行在 Node.js 中,本身没有浏览器 API,jsdom可以模拟 window、document、DOM 节点,从而支持 Vue/React 组件、DOM 交互测试。
八、总结
- Jest 是前端主流单元测试框架,上手简单,Vue/原生项目均可无缝接入;
- 落地核心:模板化编写 + AI 辅助生成,只测核心逻辑,拒绝无效测试;
- 开发准则:不依赖外部环境、不测实现细节、平衡质量与效率;
- 面试重点:掌握基础语法、
toBe/toEqual区别、异步测试、Mock 原理、项目落地思路。