服务端2026年5月31日 15:55
Jest 中 describe、test 和 it 该怎么分工?`describe` 负责分组,`test` 和 `it` 负责定义具体用例;`test` 与 `it` 在 Jest 里是同一个能力,只是表达风格不同。写测试时不要把 `describe` 当成必填包装,也不要为了层级好看一层套一层。好的组织方式是让读者从外层看到模块或场景,从内层看到行为,再从断言里确认结果。简单说,`describe` 管上下文,`test/it` 管一个可以独立失败的事实。
很多测试文件难维护,不是因为 Jest API 难,而是命名和分组把问题藏起来了。比如 `describe('utils')` 下面放几十个用例,失败时只能知道 utils 坏了,却不知道是格式化、校验还是边界值坏了。反过来,如果每个分支都新开一层 `describe`,文件会像迷宫,读者要上下跳着看 beforeEach。好的组织方式应该让失败标题连起来就是一句清楚的话。
```js
describe('Calculator.add', () => {
test('adds two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('handles negative numbers', () => {
expect(add(-2, 3)).toBe(1);
});
});
```
## 追问
### test 和 it 到底有没有功能差异?
没有,`it` 是 `test` 的别名,超时参数、异步测试、skip、only 等能力都一样。区别主要是可读性:`it('returns empty array')` 读起来像行为描述,`test('returns empty array')` 更直白。团队里最好统一一种风格,否则同一个文件里混着写会显得很乱。边界是 BDD 风格明显的组件行为测试可以用 `it`,工具函数或数据结构测试用 `test` 也很自然。取舍不是谁更高级,而是谁能让失败信息更像人话。
### describe 应该嵌套几层才合适?
通常一到两层就够了,外层放模块名或组件名,内层放方法、状态或场景。三层以上会让测试标题很长,失败输出也不容易快速定位。嵌套太深还容易把共享变量塞进外层,最后不同用例之间互相影响。更稳的做法是把复杂场景拆成多个文件,或者用清晰的测试数据工厂代替深层 `describe`。如果一个 `describe` 下面只有一个测试,也要警惕它是不是只是为了凑结构。
### beforeEach 放在 describe 里有什么坑?
`beforeEach` 适合做重复但便宜的初始化,比如创建 store、清空 mock、挂载轻量组件。不要在里面做慢请求、真实数据库连接或复杂全局配置,否则每个用例都会付一次成本。更隐蔽的坑是外层 `beforeEach` 和内层 `beforeEach` 叠加后,读者很难知道一个用例运行前到底发生了什么。只要初始化逻辑超过几行,就应该考虑抽成 `setup()`,让每个测试自己显式调用。这个取舍会让测试多写一行,但换来的是用例输入更清楚。
### 一个 test 里可以写多个 expect 吗?
可以,但多个断言应该服务于同一个行为。比如“登录成功后写入 token、更新用户信息、关闭 loading”属于一个完整结果,可以放在同一个用例里。若断言覆盖了多个原因不同的分支,失败时就很难判断到底是哪段逻辑坏了。取舍标准是:如果其中一个断言失败后,其他断言仍然代表独立需求,就拆成多个测试。另一个边界是异步流程,多个 expect 之间如果依赖执行顺序,就要确保前面的 await 已经真正结束。
### skip、only 和 todo 应该怎么用?
`test.only` 和 `describe.only` 只适合本地临时调试,不能进入主分支。`skip` 可以用于记录暂时无法运行的用例,但要写清楚原因,否则它会变成永久漏测。`todo` 适合先标记测试计划,尤其是修 bug 前先列出缺失场景。团队踩坑最多的是忘删 `.only`,建议开启 `eslint-plugin-jest` 的 `no-focused-tests` 和 `no-disabled-tests` 规则。边界是迁移期的大量失败用例,可以短期 skip,但要配 issue 或过期时间,否则它们很快没人敢动。
## 写段配置
```js
// eslint.config.js
module.exports = {
plugins: ['jest'],
extends: ['plugin:jest/recommended'],
rules: {
'jest/no-focused-tests': 'error',
'jest/no-disabled-tests': 'warn',
'jest/expect-expect': 'error'
}
};
```
组织 Jest 测试时,目标不是把层级写得像目录树,而是让失败信息能直接说明哪个行为坏了。`describe` 少而准,`test/it` 小而独立,再配合必要的 lint 约束,测试文件会比单纯追求“格式统一”更好维护。代码评审时也可以顺手看测试标题:如果只读标题看不懂行为,后面的人排查失败时也一样看不懂。标签
Jest
Jest 是一个流行的 JavaScript 测试框架,用于编写和运行测试。它由 Facebook 开发,并被应用于测试 React 组件以及其他类型的 JavaScript 代码。Jest 被设计为零配置,易于上手,同时提供了丰富的特性,如快照测试、内置的覆盖率报告和模拟系统。

服务端2026年5月31日 15:55
Jest 测试跑得太慢时该从哪些地方优化?Jest 测试变慢时,先不要急着把所有用例都改成 mock。更稳的做法是先量出慢在哪里,再从运行范围、测试环境、并发、转换缓存和外部依赖几个点逐个处理。通常收益最大的是三件事:只跑相关测试、把不需要 DOM 的用例放到 `node` 环境、把网络和计时器这类不稳定依赖隔离掉。CI 上还要控制 worker 数量,因为机器核数看起来很多,不代表同时跑满就最快,I/O、转译和内存都会抢资源。
优化前最好先固定基线:记录完整测试耗时、最慢的测试文件、是否开启 coverage、是否每次都重新转译。很多团队感觉“Jest 越来越慢”,实际是新增了 jsdom 用例、覆盖率范围过大、mock 泄漏导致重试,或者 CI 容器内存不足。把这些因素拆开之后,优化才不会变成凭感觉调参数。
```js
// jest.config.js
module.exports = {
testEnvironment: 'node',
maxWorkers: process.env.CI ? '50%' : '75%',
testTimeout: 5000,
cacheDirectory: '<rootDir>/.jest-cache',
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.stories.{js,jsx,ts,tsx}',
'!src/**/*.d.ts'
]
};
```
## 追问
### 为什么先用 --runInBand 或 --detectOpenHandles 排查,而不是直接加 maxWorkers?
`maxWorkers` 只能调度并发,不能解决测试本身卡住的问题。遇到数据库连接没关闭、定时器没清理、Promise 没 await 时,并发越高日志越乱,定位反而更慢。`--runInBand` 能把问题压成单进程复现,`--detectOpenHandles` 可以暴露遗留句柄。代价是这两个参数会明显拖慢执行速度,所以适合排查,不适合长期放进默认 CI 命令。边界也要注意:如果问题只在并发下出现,单进程可能复现不了,这时可以先降低 worker 数量,再逐步缩小到具体文件。
### testEnvironment 选 node 还是 jsdom,有什么取舍?
纯函数、Node 服务端逻辑、数据转换这类测试应该优先用 `node`,启动快、内存少,也少了 DOM 模拟层带来的噪音。组件测试、依赖 `document`、`window`、布局事件的用例才需要 `jsdom`。踩坑点是有些工具库会偷偷读取浏览器全局对象,如果全局配置成 `node`,这些用例会突然失败。比较稳的做法是默认 `node`,只在需要 DOM 的测试文件顶部用 `/** @jest-environment jsdom */` 单独声明。这样做的取舍是配置会分散一些,但换来的是大部分非 UI 测试能保持轻量。
### 覆盖率收集为什么会拖慢 Jest?
覆盖率需要对代码插桩,转译和文件扫描都会增加开销,尤其是 TypeScript 项目和大仓库更明显。日常本地开发可以不默认打开 coverage,只在提交前或 CI 的独立阶段运行。`collectCoverageFrom` 要排除声明文件、story、mock、生成代码,否则数字看起来完整,实际是在统计无意义文件。边界是核心库、支付、权限这类高风险模块仍然应该保留覆盖率门禁,不能为了速度完全取消。如果覆盖率阶段太慢,可以把单元测试和 coverage 拆成两个 CI job,让开发先拿到基础测试反馈。
### Mock 外部依赖会不会让测试失真?
会,所以 mock 要用在边界上,而不是把所有内部逻辑都替换掉。API、时间、随机数、文件系统、第三方 SDK 适合 mock,因为它们慢且不稳定;业务分支和状态变更如果也全 mock,测试就只是在验证 mock 写得对。一个常见坑是 mock 没有在 `afterEach` 里恢复,导致后面的用例继承了错误状态。可以配合 `jest.clearAllMocks()` 或 `jest.restoreAllMocks()`,让用例之间保持隔离。取舍上,少量集成测试仍然要保留真实调用链,只把网络层替换掉,这样才能发现模块之间的契约问题。
### watch、onlyChanged 和 CI 命令应该怎么分开?
本地开发追求反馈快,`jest --watch` 或 `jest --onlyChanged` 很合适,因为它们只跑和改动相关的测试。CI 追求确定性,应该跑完整测试,并把 worker、coverage、缓存目录固定下来。不要把 `test.only`、`describe.only` 当成选择性运行方案,它们很容易被误提交。团队里可以加 ESLint 规则或 pre-commit 检查禁止 `.only`,这比事后排查漏测便宜得多。还有一个边界是单体仓库:只跑 changed 可能漏掉跨包依赖,CI 最好结合依赖图或至少在合并前跑一次全量。
## 写段代码
```json
{
"scripts": {
"test": "jest --watch",
"test:changed": "jest --onlyChanged",
"test:ci": "jest --ci --coverage --maxWorkers=50%",
"test:debug": "jest --runInBand --detectOpenHandles"
}
}
```
Jest 性能优化的关键不是把命令堆满,而是给不同场景配不同命令。本地要快,CI 要稳,排查要可复现。只要把环境、并发、覆盖率和 mock 边界分清,大多数“测试越来越慢”的问题都能被压回可控范围。真正需要重写测试时,也应该先从最慢、最不稳定、最依赖外部资源的文件开始,而不是把整个测试目录推倒重来。服务端2026年5月31日 11:08
Jest 如何测试异常处理并正确使用 toThrow 和 rejects?异常测试不是为了证明代码“会报错”,而是确认它在错误输入、依赖失败和边界条件下报出正确的错,并且调用方能按预期处理。Jest 里同步异常主要用 toThrow,Promise 拒绝主要用 rejects,回调错误则要显式等待测试结束。最常见的误区是把函数先执行了,再把结果交给 expect,这样异常会在断言前就抛出。
## 同步异常怎么测?
toThrow 接收的是一个函数包装,而不是函数调用结果。可以匹配错误类型、完整消息、部分字符串或正则。
```js
function divide(a, b) {
if (b === 0) throw new RangeError('Division by zero')
return a / b
}
test('除数为 0 时抛出 RangeError', () => {
expect(() => divide(10, 0)).toThrow(RangeError)
expect(() => divide(10, 0)).toThrow(/zero/)
})
```
边界是不要过度依赖完整错误文案。文案经常为了用户体验调整,测试也会跟着碎。更稳定的断言是错误类型、错误 code,或关键短语。
## Promise 拒绝怎么测?
异步函数返回 Promise 时,用 await expect(promise).rejects...。不要忘记 await 或 return,否则测试可能在 Promise 拒绝前就结束,形成假通过。
```js
async function fetchUser(api) {
const res = await api.get('/user')
if (!res.ok) throw new Error('API Error')
return res.data
}
test('接口失败时 rejects', async () => {
const api = { get: jest.fn().mockResolvedValue({ ok: false }) }
await expect(fetchUser(api)).rejects.toThrow('API Error')
})
```
如果你需要检查错误对象上的多个字段,可以用 try/catch,但要加 expect.assertions,避免没有抛错时测试仍然通过。
```js
test('保留错误 code', async () => {
expect.assertions(2)
try {
await readConfig('/missing')
} catch (error) {
expect(error).toBeInstanceOf(Error)
expect(error.code).toBe('ENOENT')
}
})
```
## 回调错误和框架错误怎么测?
Node 风格回调可以用 done,但必须保证错误路径和成功路径都能结束测试。React 错误边界、日志上报这类场景还要临时 mock console.error,并在测试后恢复。
```js
test('callback 返回错误', done => {
loadFile('bad.txt', err => {
expect(err).toBeInstanceOf(Error)
expect(err.message).toContain('bad')
done()
})
})
```
## 追问
### toThrow 为什么必须包一层函数?
因为 Jest 需要自己调用这段代码,才能捕获抛出的异常。写成 expect(divide(1, 0)).toThrow() 时,异常已经在 expect 执行前抛出,断言根本没机会运行。这个边界只针对同步异常;异步函数即使内部 throw,也会变成 rejected Promise。踩坑是把同步和异步写法混用,导致测试报错位置看起来很奇怪。
### rejects.toThrow 和 try/catch 怎么取舍?
rejects.toThrow 简洁,适合只关心错误类型或消息的场景。try/catch 更啰嗦,但适合检查多个字段,比如 code、status、details。取舍标准是断言复杂度:一两个断言用 rejects,多字段检查用 try/catch。坑是 try/catch 里忘记 expect.assertions,当函数没有抛错时测试也可能悄悄通过。
### 错误消息应该精确匹配吗?
一般不建议完整精确匹配,除非这个消息本身就是公开 API。内部错误文案经常调整,精确匹配会让测试过于脆弱。更好的选择是匹配错误类型、错误码或关键关键词。边界是表单校验、CLI 输出、SDK 对外错误这类场景,用户依赖文案时就应该精确测试。
### 如何测试“没有抛错”?
同步函数可以写 expect(() => fn()).not.toThrow(),异步函数可以写 await expect(fn()).resolves.toEqual(...)。但不要滥用“没有抛错”作为唯一断言,因为函数可能什么也没做也能通过。取舍是它适合覆盖边界输入不崩溃,更关键的业务结果仍要单独断言。踩坑是只测 not.toThrow,漏掉返回值或副作用错误。
### Mock 抛错时要注意什么?
同步依赖用 mockImplementation(() => { throw error }),异步依赖用 mockRejectedValue(error),不要混着用。错误对象最好带上真实业务会用到的字段,比如 code 或 response.status。边界是有些库抛出的不是 Error,而是普通对象,测试要和真实库行为一致。踩坑是 mock 得太理想,生产里的错误结构不同,catch 分支读取字段时再次报错。服务端2026年5月31日 11:08
Jest 如何测试 TypeScript 项目并配置 ts-jest?Jest 测 TypeScript 项目时,先要分清两个问题:测试运行时怎么把 TS 转成 JS,以及类型错误由谁负责检查。ts-jest 可以在 Jest 运行时编译 TypeScript,配置直观,适合希望测试和 tsconfig 保持一致的项目。另一个常见选择是 Babel 或 SWC 转译,它们更快,但通常不做完整类型检查。
## ts-jest 基础配置怎么写?
先安装 Jest、类型声明和 ts-jest。Node 项目一般使用 node 环境,前端组件或 DOM 工具才需要 jsdom。
```bash
npm i -D jest ts-jest @types/jest typescript
```
```js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts']
}
```
tsconfig.json 里要包含 Jest 类型,否则 describe、test、expect 可能被编辑器标红。
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"types": ["jest", "node"]
}
}
```
如果项目本身是 ESM,配置会更敏感,需要使用 preset: 'ts-jest/presets/default-esm',并处理 extensionsToTreatAsEsm。这是 ts-jest 里最常见的坑之一。
## 类型安全的测试怎么写?
测试不应该为了通过而到处 as any。TypeScript 的价值在于让测试数据、Mock 和返回值也遵守业务类型。
```ts
export interface User { id: number; name: string }
export function formatUser(user: User): string {
return `${user.id}:${user.name}`
}
test('格式化用户信息', () => {
const user: User = { id: 1, name: 'Ada' }
expect(formatUser(user)).toBe('1:Ada')
})
```
Mock 函数可以用 jest.MockedFunction 或 jest.mocked,避免 mockResolvedValue 的类型丢失。
```ts
import { fetchUser } from './api'
jest.mock('./api')
const mockedFetchUser = jest.mocked(fetchUser)
test('加载用户', async () => {
mockedFetchUser.mockResolvedValue({ id: 1, name: 'Ada' })
await expect(loadUserName(1)).resolves.toBe('Ada')
})
```
## 要不要让 Jest 做类型检查?
ts-jest 可以诊断类型错误,但大型项目里会拖慢测试。很多团队会把 tsc --noEmit 放到 CI 的单独步骤,让 Jest 专注行为测试。这样失败信息更清晰:类型错归类型检查,逻辑错归单元测试。
## 追问
### ts-jest、babel-jest 和 swc-jest 怎么选?
ts-jest 最贴近 TypeScript 编译器,路径、装饰器和部分 tsconfig 行为更容易对齐。Babel 或 SWC 通常更快,适合大型前端项目,但它们主要是转译,不负责完整类型检查。取舍是准确性和速度:配置复杂、依赖 TS 编译特性的项目优先 ts-jest;追求测试速度并已有独立 tsc --noEmit 的项目可以选 SWC。踩坑是以为 Jest 通过就代表类型没问题,实际上转译型方案可能放过类型错误。
### 路径别名为什么在测试里经常失效?
TypeScript 的 paths 只告诉编译器怎么解析,不会自动教 Jest 解析模块。Jest 需要单独配置 moduleNameMapper,或者用 pathsToModuleNameMapper 从 tsconfig 生成。边界是 monorepo 里 rootDir、baseUrl 和包边界更复杂,不能简单复制单包配置。常见坑是源码能编译,测试却报 Cannot find module '@/xxx'。
### ESM 项目配置 Jest 有什么坑?
ESM 下 type: module、tsconfig 的 module、Jest preset 和导入扩展名必须互相匹配。很多错误不是业务代码错,而是 CJS/ESM 混用导致模块加载失败。取舍是如果项目没有强 ESM 需求,测试环境保持 CJS 会省事很多;如果库要发布 ESM,就应该尽早把测试跑在接近发布格式的环境里。踩坑是 mock ESM 模块方式和 CJS 不同,旧的 jest.mock 习惯可能失效。
### TypeScript 测试里什么时候可以用 as any?
as any 可以用于刻意构造非法输入,测试运行时防御逻辑,例如后端收到脏数据。除此之外应尽量避免,因为它会绕开类型系统,让测试数据变得不可信。取舍是:为了测边界可以局部使用,但要用注释说明这是故意破坏类型。踩坑是为了省事大量 as any,最后测试覆盖了一个现实中根本不会出现的类型形状。
### 类型测试和行为测试要分开吗?
通常要分开。Jest 擅长测运行时行为,类型层面的断言可以用 tsd、expect-type 或 tsc --noEmit。边界是工具库、SDK、泛型函数这类类型就是产品能力的代码,应该补类型测试。普通业务项目则不必把所有类型都放进 Jest,否则会让测试意图变模糊。服务端2026年5月31日 11:08
Jest 如何测试 fs 文件系统和 I/O 操作?测试文件系统代码时,最重要的问题不是“能不能读写文件”,而是“你的业务逻辑在文件存在、缺失、权限不足、内容损坏时是否处理正确”。Jest 可以 mock fs,也可以配合临时目录做接近真实的集成测试。两种方式都该会用,因为纯 Mock 快但容易脱离真实行为,真实 I/O 准但慢且需要清理。
## 什么时候 Mock fs?
如果函数只是包装读取、解析和错误处理,mock fs 很合适。它能让测试不依赖本机路径,也不会污染项目目录。
```js
const fs = require('fs')
jest.mock('fs')
function readConfig(path) {
const raw = fs.readFileSync(path, 'utf8')
return JSON.parse(raw)
}
test('读取并解析配置文件', () => {
fs.readFileSync.mockReturnValue('{"port":3000}')
expect(readConfig('/app/config.json')).toEqual({ port: 3000 })
expect(fs.readFileSync).toHaveBeenCalledWith('/app/config.json', 'utf8')
})
```
边界是:mock 出来的 fs 不会模拟所有 Node 行为,比如真实错误对象的 code、路径分隔符、权限差异、符号链接。只靠 mock,可能会漏掉生产环境里的路径问题。
## 异步 fs.promises 怎么测?
现代 Node 项目更常用 fs/promises。这时要 mock 对应模块,并用 mockResolvedValue 或 mockRejectedValue 表达成功和失败。
```js
jest.mock('fs/promises', () => ({
readFile: jest.fn(),
writeFile: jest.fn(),
mkdir: jest.fn()
}))
const fs = require('fs/promises')
test('文件不存在时返回默认配置', async () => {
fs.readFile.mockRejectedValue(Object.assign(new Error('missing'), { code: 'ENOENT' }))
await expect(loadConfig('/no-file.json')).resolves.toEqual({})
})
```
这里的坑是不要只 mock happy path。I/O 最容易出问题的地方恰恰是 ENOENT、EACCES、JSON 格式错误、磁盘写入中断和目录不存在。
## 什么时候用真实临时文件?
涉及路径拼接、目录创建、文件遍历、编码、换行符或原子写入时,最好用临时目录跑一层集成测试。
```js
const os = require('os')
const path = require('path')
const fs = require('fs/promises')
test('写入后可以再次读取', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'jest-fs-'))
const file = path.join(dir, 'data.txt')
await saveText(file, 'hello')
await expect(fs.readFile(file, 'utf8')).resolves.toBe('hello')
await fs.rm(dir, { recursive: true, force: true })
})
```
清理必须放在 afterEach 或 try/finally 中,否则 CI 上一次失败会影响下一次运行。
## 追问
### Mock fs 和使用临时目录怎么取舍?
Mock fs 速度快、断言清晰,适合覆盖业务分支和错误处理。临时目录更接近真实环境,适合验证路径、编码、目录递归和跨平台行为。取舍标准是测试目标:想验证“函数是否调用了 fs”用 Mock,想验证“文件结果是否真的正确”用临时目录。踩坑是把所有测试都写成真实 I/O,最后测试套件变慢且偶发失败。
### 如何测试文件不存在和权限不足?
不要只断言抛错,要断言你的代码对错误的处理策略。文件不存在可能返回默认值,也可能提示用户创建配置;权限不足通常应该向上抛出更明确的错误。边界是不同系统的错误消息不稳定,不要用完整 message 做强匹配。更稳的是匹配 error.code,例如 ENOENT 或 EACCES。
### 为什么手动 mock fs 容易失真?
Node 的 fs API 细节很多,同步、异步、callback、promise 版本行为并不完全一样。手动 mock 只覆盖你想到的分支,没想到的地方会变成测试盲区。取舍是:小函数手动 mock 足够,大量文件操作可以考虑 memfs 这类内存文件系统。坑是 memfs 也不是完整操作系统,权限和符号链接仍可能与真实环境不同。
### 文件遍历测试要注意什么边界?
目录遍历要测空目录、嵌套目录、隐藏文件、扩展名过滤和排序稳定性。跨平台时还要避免硬编码 /,应该使用 path.join 或 path.resolve。取舍是测试里是否固定排序:如果业务需要稳定输出,就应该排序并测试;如果不需要,断言可以用集合方式。踩坑是 macOS 本地文件顺序和 Linux CI 不一致,导致测试偶发失败。
### 写入文件时如何避免测试污染?
每个测试都应创建独立临时目录,不要共用项目里的 fixtures/output。清理逻辑要放在 finally 或 afterEach,即使断言失败也能删除文件。边界是调试失败时你可能想保留文件,可以通过环境变量控制是否清理。不要在测试里写固定路径,尤其不要写用户主目录或仓库根目录。服务端2026年5月31日 11:08
Jest 如何测试 Redux 的 Action、Reducer 和 Selector?Redux 测试最好按代码职责拆开:action creator 测返回的 action,reducer 测状态如何变化,selector 测派生数据,异步 thunk 或 RTK Query 再测副作用边界。不要把所有东西都塞进一个 React 组件测试里,否则失败时很难判断是 UI、store、接口 Mock,还是 reducer 写错了。Jest 的价值在于让这些纯逻辑可以被快速、稳定地验证。
## 从 reducer 开始最划算
reducer 通常是纯函数,输入旧 state 和 action,输出新 state。它不需要 DOM,也不需要 mock store,是 Redux 里最适合单元测试的部分。
```js
const initialState = { count: 0 }
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'counter/increment':
return { ...state, count: state.count + 1 }
case 'counter/add':
return { ...state, count: state.count + action.payload }
default:
return state
}
}
test('处理 add action', () => {
expect(counterReducer({ count: 2 }, { type: 'counter/add', payload: 3 }))
.toEqual({ count: 5 })
})
```
这里要特别测 default 分支和不可变更新。一个常见坑是 reducer 直接修改原对象,简单断言结果可能看不出来,但会影响 React-Redux 的引用比较。可以在测试里冻结输入对象,或者使用 Redux Toolkit 的 Immer,让写法和不可变语义保持一致。
## action 和 selector 怎么测?
action creator 如果只是返回对象,测试应保持克制;过度测试会让代码变得像快照翻译。selector 更值得测,因为它经常藏着筛选、排序、权限判断和空值兼容。
```js
export const addTodo = text => ({ type: 'todos/add', payload: text })
export const selectDoneTodos = state => state.todos.items.filter(x => x.done)
test('创建 addTodo action', () => {
expect(addTodo('写测试')).toEqual({ type: 'todos/add', payload: '写测试' })
})
test('筛选已完成 todos', () => {
const state = { todos: { items: [{ done: true }, { done: false }] } }
expect(selectDoneTodos(state)).toHaveLength(1)
})
```
selector 的边界包括空数组、缺字段、排序稳定性和 memoized selector 的引用复用。尤其是 Reselect,如果 selector 每次都返回新数组,组件会无意义重渲染。
## 异步逻辑如何隔离?
传统 thunk 可以 mock API,再断言 dispatch 顺序。Redux Toolkit 的 createAsyncThunk 更推荐测 fulfilled/rejected 对 reducer 的影响,少测内部实现。
```js
test('fetchUser 成功后派发 success', async () => {
const api = { getUser: jest.fn().mockResolvedValue({ id: 1 }) }
const dispatch = jest.fn()
await fetchUser(1, api)(dispatch)
expect(dispatch).toHaveBeenCalledWith({ type: 'user/success', payload: { id: 1 } })
})
```
## 追问
### 为什么 reducer 测试比 action 测试更重要?
reducer 承担业务规则,状态加减、列表合并、错误回滚都在这里发生。action creator 很多时候只是对象工厂,测太细会带来重复断言。取舍是:复杂 action payload 需要测试,简单 action 可以靠 reducer 或集成测试覆盖。踩坑是只测 action 不测 reducer,最后 action 发出去了但状态根本没变。
### selector 应该测返回值还是 memoization?
普通 selector 测返回值即可,重点是不同 state 下结果是否符合业务预期。使用 Reselect 时,可以补一两个引用稳定性的测试,确认相同输入不会重新生成对象。边界是不要把 memoization 细节测得过死,否则换实现时测试会阻碍重构。真正有性能风险的 selector,例如大列表过滤和权限树计算,才值得额外测缓存行为。
### mock store 和真实 store 怎么选?
mock store 适合验证 thunk 派发了哪些 action,速度快,也容易断言顺序。真实 store 更适合验证 reducer、middleware 和组件一起工作后的最终 UI。取舍点是你关心“过程”还是“结果”:过程用 mock store,结果用真实 store。坑在于 redux-mock-store 不会真的更新 state,拿它测组件状态变化会得到误导性结果。
### Redux Toolkit 的 slice 还需要单独测 action 吗?
大多数情况下不需要单独测自动生成的 action creator,因为它们由库保证。更有价值的是测试 slice reducer 对 pending、fulfilled、rejected 的处理,以及 payload 边界。边界是如果你写了 prepare 回调,它包含格式化、生成 id 或清洗输入,就应该单测。否则测试越多越像在测试 Redux Toolkit 本身。
### React-Redux 组件测试要 mock useSelector 吗?
可以 mock,但不建议作为默认方案。mock useSelector 很快,却会绕过 Provider、store shape 和订阅更新,容易让测试与真实运行环境脱节。更稳的方式是创建一个测试 store,用 Provider 包住组件,然后从用户视角断言页面。只有组件很小、依赖 state 很简单,并且你明确只想隔离 UI 分支时,mock hook 才是合理取舍。服务端2026年5月31日 11:08
Jest 如何配合 Vue Test Utils 测试 Vue 组件?Vue 组件测试的核心不是把组件内部每一行都测一遍,而是验证它对外表现是否稳定:传入 props 后渲染什么,用户点击后发生什么,是否 emit 正确事件,依赖插件或异步更新时是否能按预期收尾。Jest 负责断言、Mock 和运行环境,@vue/test-utils 负责把 Vue 组件挂载成可操作的 wrapper。实际项目里,测试越贴近用户行为,后期重构越不容易被测试绑住。
## 基础配置怎么写?
Vue 3 项目常见组合是 Jest、@vue/test-utils 和 @vue/vue3-jest。如果还在 Vue 2,需要换成对应版本的 vue-jest,这是最容易踩的版本坑。
```bash
npm i -D jest @vue/test-utils @vue/vue3-jest babel-jest jest-environment-jsdom
```
```js
module.exports = {
testEnvironment: 'jsdom',
moduleFileExtensions: ['js', 'json', 'vue'],
transform: {
'^.+\\.vue$': '@vue/vue3-jest',
'^.+\\.js$': 'babel-jest'
},
moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' }
}
```
这里的边界是:Jest 跑在 jsdom 中,不是真浏览器。它适合测组件逻辑和 DOM 结果,不适合验证真实布局、滚动位置、CSS 动画或浏览器兼容性;这些应交给 E2E 或视觉回归测试。
## 一个组件应该测什么?
以计数器为例,优先测用户能感知的结果,而不是直接断言 wrapper.vm.count。这样即使以后把 Options API 改成 Composition API,只要 UI 行为不变,测试仍然有效。
```js
import { mount } from '@vue/test-utils'
import Counter from '@/components/Counter.vue'
test('点击按钮后展示新的计数并派发事件', async () => {
const wrapper = mount(Counter, { props: { initialCount: 1 } })
await wrapper.get('button').trigger('click')
expect(wrapper.text()).toContain('Count: 2')
expect(wrapper.emitted('change')?.[0]).toEqual([2])
})
```
trigger 和 setProps 后通常要 await,因为 Vue DOM 更新是异步的。漏掉 await 时,测试可能本地偶尔通过、CI 偶尔失败,这类 flaky case 很难排查。
## 追问
### mount 和 shallowMount 应该怎么取舍?
mount 会渲染子组件,适合验证父子组合后的真实行为,例如表单组件和校验提示是否一起工作。shallowMount 会把子组件替换成 stub,速度更快,也能让测试只关注当前组件。取舍点在于测试目标:如果失败原因应该定位到当前组件,用 shallowMount;如果用户路径依赖子组件交互,用 mount。踩坑是过度使用 shallowMount 会把插槽、provide/inject 或子组件事件遮掉,导致测试通过但页面真实不可用。
### 为什么不建议大量断言 wrapper.vm?
wrapper.vm 能拿到组件实例,测 computed 或方法很方便,但它会把测试绑定到实现细节。组件重构后,用户看到的页面没变,测试却因为内部变量名变化而失败,这就是维护成本。边界是复杂计算逻辑如果没有抽成纯函数,又确实需要覆盖,可以少量使用 wrapper.vm。更稳的做法是通过文本、属性、事件和可访问角色来断言外部行为。
### 异步接口和插件依赖怎么测?
接口请求通常用 jest.fn() 或 MSW Mock,不要让单元测试真的访问网络。Vue Router、Pinia、i18n 这类插件可以通过 global.plugins 注入,也可以只 Mock 当前组件用到的最小能力。取舍在于真实性和速度:插件全量接入更像真实页面,但配置重、失败链路长;局部 Mock 更快,但容易漏掉集成问题。常见坑是异步 Promise 已经 resolve,但 Vue 还没完成 DOM 更新,需要配合 flushPromises() 和 await nextTick()。
### 测 props、slots 和 emitted 时重点是什么?
props 要测边界值,例如空字符串、缺省值、禁用状态,而不只是正常值。slots 要确认组件是否把外部内容放在正确位置,尤其是弹窗、表格列和布局组件。emitted 不只看有没有触发,还要看 payload 是否稳定,因为父组件往往依赖这个契约。踩坑是事件名大小写和 Vue 版本差异,模板里写法和测试里读取的事件名要保持一致。
### 什么时候该改用组件集成测试或 E2E?
当测试需要覆盖路由跳转、真实接口拦截、浏览器焦点、拖拽、文件上传时,单靠 Jest 会越来越别扭。Jest 的优势是快、反馈短,适合把组件的输入输出守住。E2E 更慢但更真实,适合覆盖关键业务链路。好的边界是:组件内部状态交给 Jest,跨页面和浏览器能力交给 Playwright 或 Cypress。前端5月28日 07:00
如何在 Jest 中测试 React 组件?常用的测试工具和查询方法有哪些?在 Jest 中测试 React 组件,核心思路是:渲染组件 → 查询元素 → 断言行为。React 官方推荐的测试方案是 Jest + React Testing Library(RTL),本文聚焦面试中高频考察的知识点。
## React 组件测试的基本流程是什么?
测试 React 组件通常分三步:
1. **渲染**:使用 RTL 的 `render` 方法将组件挂载到虚拟 DOM
2. **查询**:通过 `screen` 对象提供的方法定位页面元素
3. **断言**:使用 Jest 的 `expect` 验证元素状态或行为
```javascript
import { render, screen } from '@testing-library/react';
import Counter from './Counter';
test('counter displays initial value', () => {
render(<Counter initialCount={0} />);
expect(screen.getByText('Count: 0')).toBeInTheDocument();
});
```
## 查询方法的优先级怎么选?
RTL 的查询方法有三个前缀,区别在于元素不存在时的行为:
| 前缀 | 元素存在 | 元素不存在 | 适用场景 |
|------|---------|-----------|---------|
| `getBy*` | 返回元素 | 抛出错误 | 断言元素一定存在 |
| `queryBy*` | 返回元素 | 返回 null | 断言元素不存在 |
| `findBy*` | 返回 Promise | Promise reject | 异步元素出现 |
具体查询方法的推荐优先级:
1. **`getByRole`** — 最优先,基于 ARIA 角色,如 `button`、`textbox`、`heading`
2. **`getByLabelText`** — 表单元素优先用,关联 label 文本
3. **`getByPlaceholderText`** — 没有 label 时使用
4. **`getByText`** — 非表单元素(按钮、链接、段落)常用
5. **`getByTestId`** — 最后手段,需要手动添加 `data-testid` 属性
```javascript
// 推荐:通过角色查询
screen.getByRole('button', { name: /submit/i });
// 不推荐但有时必要:通过 testId 查询
screen.getByTestId('submit-btn');
```
**面试关键点**:优先使用 `getByRole` 是因为它验证了组件的可访问性,这与 RTL "测试用户视角" 的核心理念一致。
## 如何测试用户交互?
使用 `fireEvent` 或 `userEvent` 模拟用户操作。`userEvent` 更接近真实用户行为,推荐优先使用。
```javascript
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('clicking button increments counter', async () => {
const user = userEvent.setup();
render(<Counter initialCount={0} />);
await user.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
```
`fireEvent` 与 `userEvent` 的区别:
- `fireEvent.click()` 只触发 click 事件
- `userEvent.click()` 会依次触发 mousedown → mouseup → focus → click,更贴近真实操作
- `userEvent.type()` 会逐字符触发键盘事件,而 `fireEvent.change()` 直接修改值
## 异步组件怎么测试?
异步场景(接口请求、定时器、状态延迟更新)使用 `waitFor` 或 `findBy*` 处理。
```javascript
import { render, screen, waitFor } from '@testing-library/react';
test('displays user data after loading', async () => {
render(<UserProfile userId={1} />);
// 方式一:findBy(推荐,更简洁)
expect(await screen.findByText('John')).toBeInTheDocument();
// 方式二:waitFor(更灵活,可组合多个断言)
await waitFor(() => {
expect(screen.getByText('John')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
});
```
**常见坑**:`waitFor` 中不要用 `queryBy*`,因为它不抛错,断言不会失败,导致测试误通过。应使用 `getBy*`。
## 如何 Mock 模块和 API 请求?
面试中常考的 Mock 手段分两种:
**Jest.fn() — Mock 函数**
```javascript
test('calls onSubmit with form data', async () => {
const onSubmit = jest.fn();
const user = userEvent.setup();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.click(screen.getByRole('button', { name: /login/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com'
});
});
```
**jest.mock — Mock 模块**
```javascript
// Mock API 请求模块
jest.mock('../api', () => ({
fetchUser: jest.fn().mockResolvedValue({ name: 'John' })
}));
test('renders fetched user name', async () => {
render(<UserProfile />);
expect(await screen.findByText('John')).toBeInTheDocument();
});
```
对于更复杂的 API Mock 场景,可以使用 Mock Service Worker(MSW),它在 Service Worker 层拦截请求,不需要修改业务代码。
## React Hooks 怎么测试?
自定义 Hook 使用 `renderHook` 进行测试:
```javascript
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
test('useCounter increments and decrements', () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(0);
});
```
**注意**:状态更新必须包裹在 `act()` 中,否则 Jest 会报警告。`renderHook` 已从 RTL v13 起内置,不再需要 `@testing-library/react-hooks` 包。
## 快照测试怎么用?什么场景下用?
```javascript
import renderer from 'react-test-renderer';
test('Button matches snapshot', () => {
const tree = renderer.create(<Button>Click</Button>).toJSON();
expect(tree).toMatchSnapshot();
});
```
快照测试的适用与不适用:
- 适合:配置型组件(Theme、Layout),结构稳定的纯展示组件
- 不适合:频繁变动的业务组件,否则每次改动都要更新快照,失去测试价值
**面试加分点**:快照测试只是确认结构没变,并不验证行为是否正确,所以不能替代行为测试。
## 测试 React 组件有哪些最佳实践?
1. **测试行为,不测实现** — 不测内部 state 的值,测用户看到的结果
2. **避免过度 Mock** — Mock 越多,测试离真实场景越远
3. **查询方法按优先级选** — `getByRole` > `getByLabelText` > `getByText` > `getByTestId`
4. **异步用 `findBy` 优于 `waitFor` + `getBy`** — 更简洁,语义更清晰
5. **使用 `screen` 而非 `render` 返回值** — 避免反复解构,代码更干净
6. **一个测试只验证一个行为** — 方便定位失败原因
**面试追问方向**:如何测试 Context Provider 包裹的组件?如何处理第三方库的渲染行为?如何在 CI 中提升测试执行速度?这些是区分中级与高级的关键问题。前端5月28日 06:59
如何在 Jest 中 Mock fetch 和 Axios 测试 API 调用?## 核心思路
测试 API 调用的关键原则是**隔离外部依赖**——不发出真实网络请求,用 Mock 替代,验证的是"你的代码如何调用 API、如何处理响应",而非 API 本身的行为。
Jest 提供了三种主要 Mock 手段:`jest.mock()` 模块级替换、`jest.spyOn()` 方法级监听、`jest.fn()` 手动创建假函数。理解三者的区别和适用场景,是这道题的答题主线。
## Mock Axios 的两种方式
### 方式一:jest.mock() 替换整个模块
`jest.mock('axios')` 会将 axios 模块中所有导出替换为 jest.fn(),适合需要完全控制模块行为的场景:
```javascript
import axios from 'axios';
import { getUser } from './api';
jest.mock('axios');
test('getUser 应返回用户数据', async () => {
const mockData = { id: 1, name: 'Tom' };
axios.get.mockResolvedValue({ data: mockData });
const result = await getUser(1);
expect(result).toEqual(mockData);
expect(axios.get).toHaveBeenCalledWith('/users/1');
});
```
`mockResolvedValue` 让 `axios.get` 返回一个 resolved Promise,模拟成功响应。`toHaveBeenCalledWith` 断言调用参数,确保请求地址正确。
### 方式二:jest.spyOn() 监听原方法
`jest.spyOn` 不替换模块,而是包装原方法,可以追踪调用并控制返回值,还能通过 `mockRestore()` 恢复原实现:
```javascript
import axios from 'axios';
import { getUser } from './api';
test('getUser 应返回用户数据', async () => {
const spy = jest.spyOn(axios, 'get').mockResolvedValue({ data: { id: 1, name: 'Tom' } });
const result = await getUser(1);
expect(result).toEqual({ id: 1, name: 'Tom' });
spy.mockRestore(); // 恢复 axios.get 原实现
});
```
**何时选哪个?** `jest.mock()` 适合整个测试文件都需要 mock 的场景;`jest.spyOn()` 适合只想在单个测试中临时 mock、其余测试保留真实行为的场景。
## Mock fetch 的两种方式
### 方式一:jest.fn() 替换全局 fetch
fetch 是全局对象上的方法,直接赋值即可替换:
```javascript
import { fetchPosts } from './api';
beforeEach(() => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{ id: 1, title: 'Hello' }]),
})
);
});
afterEach(() => {
jest.restoreAllMocks();
});
test('fetchPosts 应返回帖子列表', async () => {
const posts = await fetchPosts();
expect(posts).toEqual([{ id: 1, title: 'Hello' }]);
expect(global.fetch).toHaveBeenCalledWith('/api/posts');
});
```
这里用 `beforeEach` / `afterEach` 管理 Mock 生命周期,避免测试间互相污染——这是面试中经常追问的考点。
### 方式二:jest.spyOn() 监听全局 fetch
```javascript
test('fetchPosts 处理响应数据', async () => {
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
json: () => Promise.resolve([{ id: 1 }]),
});
const posts = await fetchPosts();
expect(posts).toEqual([{ id: 1 }]);
});
```
## 测试错误场景
只测成功路径是不够的,面试官一定会问"网络请求失败了怎么办":
```javascript
test('getUser 应抛出网络错误', async () => {
axios.get.mockRejectedValue(new Error('Network Error'));
await expect(getUser(1)).rejects.toThrow('Network Error');
});
test('getUser 应处理 404 响应', async () => {
axios.get.mockRejectedValue({
response: { status: 404, data: { message: 'Not Found' } },
});
await expect(getUser(999)).rejects.toMatchObject({
response: { status: 404 },
});
});
```
`mockRejectedValue` 模拟 Promise reject,覆盖网络异常和服务端错误两种情况。
## 使用 MSW 做更真实的拦截
当项目有大量 API 需要测试时,逐个 `jest.mock` 维护成本高。MSW(Mock Service Worker)在网络层拦截请求,不需要修改业务代码:
```javascript
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(ctx.json({ id: req.params.id, name: 'Tom' }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('getUser 通过 MSW 返回数据', async () => {
const user = await getUser(1);
expect(user).toEqual({ id: '1', name: 'Tom' });
});
```
MSW 的优势:可以在运行时动态修改响应(`server.use()`),测试超时、限流等边界场景;同一套 handler 可复用于单元测试和集成测试。
## 关键差异速查
| 场景 | 推荐方案 | 原因 |
|------|---------|------|
| Mock 整个第三方库 | `jest.mock()` | 一键替换所有导出 |
| 单个测试临时 Mock | `jest.spyOn()` | 可恢复,不影响其他测试 |
| Mock 全局 API(fetch) | `jest.fn()` / `spyOn` | fetch 是全局变量,需手动处理 |
| 大量 API 集成测试 | MSW | 网络层拦截,维护成本低 |
## 面试追问方向
- **jest.mock 和 jest.spyOn 的本质区别?** mock 是替换,spyOn 是包装。mock 后原实现丢失,spyOn 可恢复。
- **为什么要避免测试中发出真实请求?** 网络不稳定、速度慢、可能产生脏数据、依赖外部服务可用性。
- **Mock 污染怎么解决?** beforeEach 重置、afterEach 调用 `jest.restoreAllMocks()`、每个测试独立设置数据。
- **如何测试请求重试逻辑?** 用 `mockRejectedValueOnce` 连续返回失败,最后一次返回成功,模拟重试后恢复。服务端5月28日 05:27
如何配置 Jest?常用配置选项有哪些?Jest 有三种配置方式:`package.json` 的 `jest` 字段、独立的 `jest.config.js`(或 `.ts`/`.json`/`.mjs`)文件、以及 CLI 参数 `--config`。实际项目中 90% 用 `jest.config.js`,因为可读性好、能写注释、支持条件逻辑。
核心配置项按优先级说:
**testEnvironment** — 决定测试运行环境。`node` 适合纯逻辑(工具函数、后端),`jsdom` 模拟浏览器 DOM(React 组件、DOM 操作)。选错会导致全局对象找不到或内存飙升。Next.js 项目用 `@jest/globals` 里的 `customExportConditions` 可以按组件区分环境。
**transform** — 告诉 Jest 用什么转换器处理非 JS 文件。`babel-jest` 是默认值,TypeScript 项目换成 `ts-jest` 或用 `@swc/jest` 加速。配错了表现为 `SyntaxError: Unexpected token`。
**moduleNameMapper** — 路径别名映射。配 Webpack/Vite 的 `@/` 前缀、CSS/图片等静态资源的 mock 都靠它。最常见写法:`'^@/(.*)$': '<rootDir>/src/$1'`,静态资源用 `identity-obj-proxy`。
**transformIgnorePatterns** — 指定哪些文件不做转换。默认忽略整个 `node_modules`,但 ESM 包(如 `lodash-es`、`axios`)没编译成 CJS 就会报错。解法是用负向先行断言:`'/node_modules/(?!(lodash-es|axios)/)'`。
**setupFilesAfterEnv** — 测试环境初始化后执行的脚本,用来引入 `@testing-library/jest-dom` 的扩展匹配器、全局 mock `window.matchMedia` 等。区别于 `setupFiles`(在测试框架加载前运行,一般用不到)。
**coverageThreshold** — 覆盖率门禁。团队规范通常设 `branches: 80, functions: 80, lines: 80`,CI 中低于阈值直接失败。
**preset** — 一键继承预置配置。`ts-jest` 提供 `preset: 'ts-jest'`,React 项目用 `react-app`(CRA)或 `@testing-library/react/jest-dom`。preset 和手动配置重复时,手动配置优先。
**projects** — monorepo 专属,每个子项目可以独立配置 testEnvironment、transform 等,Jest 并行跑所有项目。
## 追问
### testEnvironment 选 node 还是 jsdom 怎么决定?
跑纯函数、Node API 用 `node`;涉及 DOM 操作、React 渲染用 `jsdom`。`jsdom` 内存开销大,API 不完整(没有 `canvas` 布局、`IntersectionObserver`),需要额外 mock。同一个项目可以按目录分 projects 配不同环境。
### transformIgnorePatterns 配了但不生效怎么办?
先跑 `npx jest --showConfig` 看实际合并后的配置,preset 可能覆盖了你的设置。常见坑:正则里的路径分隔符在 Windows 上不一致,或者忘了负向断言里的 `/`。清缓存 `jest --clearCache` 再试。
### Jest 跑 ESM 包一直报 SyntaxError 怎么排查?
三步:1)确认 `transform` 配了对应转换器;2)检查 `transformIgnorePatterns` 是否把该包排除了忽略列表;3)如果包本身只导出 ESM,考虑用 `moduleNameMapper` 指向 CJS 入口或者直接 mock 掉。
### monorepo 里 packages 互相依赖怎么配 Jest?
用 `projects` 配置,每个 package 指定自己的 `rootDir` 和 `testMatch`。packages 间依赖通过 `moduleNameMapper` 映射到源码目录而不是 `dist`,这样改了依赖包的代码测试立即生效。
## 写段代码
```js
// jest.config.js — React + TS 项目典型配置
module.exports = {
testEnvironment: 'jsdom',
transform: { '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest' },
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less)$': 'identity-obj-proxy',
},
transformIgnorePatterns: [
'/node_modules/(?!(lodash-es)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
coverageThreshold: {
global: { branches: 80, functions: 80, lines: 80 },
},
};
```