neo-cmp-cli 1.12.7 → 1.12.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +204 -6
- package/dist/index2.js +1 -1
- package/dist/neo/env.js +1 -1
- package/dist/package.json.js +1 -1
- package/package.json +1 -1
- package/template/antd-custom-cmp-template/package.json +1 -1
- package/template/{neo-bi-cmps → asset-manage-template}/README.md +65 -10
- package/template/asset-manage-template/docs/README.md +244 -0
- package/template/asset-manage-template/neo.config.js +60 -0
- package/template/{neo-bi-cmps → asset-manage-template}/package.json +28 -16
- package/template/asset-manage-template/src/assets/img/chart.svg +1 -0
- package/template/asset-manage-template/src/components/README.md +3 -0
- package/template/asset-manage-template/src/components/assetManage__c/assetApi.ts +70 -0
- package/template/asset-manage-template/src/components/assetManage__c/cmps/AssetCreateModal.tsx +260 -0
- package/template/asset-manage-template/src/components/assetManage__c/cmps/AssetGrid.tsx +48 -0
- package/template/asset-manage-template/src/components/assetManage__c/cmps/AssetSidebar.tsx +74 -0
- package/template/asset-manage-template/src/components/assetManage__c/cmps/AssetToolbar.tsx +79 -0
- package/template/asset-manage-template/src/components/assetManage__c/cmps/assetDisplay.tsx +72 -0
- package/template/asset-manage-template/src/components/assetManage__c/constants.ts +28 -0
- package/template/asset-manage-template/src/components/assetManage__c/index.tsx +258 -0
- package/template/asset-manage-template/src/components/assetManage__c/model.ts +75 -0
- package/template/asset-manage-template/src/components/assetManage__c/style.scss +425 -0
- package/template/asset-manage-template/src/components/assetManage__c/types.ts +60 -0
- package/template/asset-manage-template/src/components/bidList__c/cmps/BidCard.tsx +47 -0
- package/template/asset-manage-template/src/components/bidList__c/constants.ts +6 -0
- package/template/asset-manage-template/src/components/bidList__c/formatUtils.ts +14 -0
- package/template/asset-manage-template/src/components/bidList__c/index.tsx +194 -0
- package/template/asset-manage-template/src/components/bidList__c/model.ts +57 -0
- package/template/asset-manage-template/src/components/bidList__c/style.scss +179 -0
- package/template/asset-manage-template/src/components/bidList__c/types.ts +10 -0
- package/template/asset-manage-template/src/components/bidPackage__c/cmps/BidPackageHeader.tsx +140 -0
- package/template/asset-manage-template/src/components/bidPackage__c/cmps/PackageItemTable.tsx +148 -0
- package/template/asset-manage-template/src/components/bidPackage__c/index.tsx +394 -0
- package/template/asset-manage-template/src/components/bidPackage__c/mainTableColumns.tsx +57 -0
- package/template/asset-manage-template/src/components/bidPackage__c/model.ts +86 -0
- package/template/asset-manage-template/src/components/bidPackage__c/style.scss +256 -0
- package/template/asset-manage-template/src/components/bidPackage__c/types.ts +35 -0
- package/template/asset-manage-template/src/components/bidPackage__c/utils.ts +19 -0
- package/template/{neo-bi-cmps → asset-manage-template}/src/utils/axiosFetcher.ts +0 -0
- package/template/{neo-bi-cmps → asset-manage-template}/src/utils/queryObjectData.ts +0 -0
- package/template/asset-manage-template/src/utils/url.ts +82 -0
- package/template/{neo-bi-cmps → asset-manage-template}/src/utils/xobjects.ts +0 -0
- package/template/{neo-bi-cmps → asset-manage-template}/tsconfig.json +1 -1
- package/template/echarts-custom-cmp-template/package.json +1 -1
- package/template/empty-custom-cmp-template/package.json +2 -2
- package/template/neo-custom-cmp-template/package.json +2 -2
- package/template/neo-custom-cmp-template/src/components/entityTable__c/index.tsx +62 -6
- package/template/neo-h5-cmps/neo.config.js +34 -76
- package/template/neo-h5-cmps/package.json +7 -3
- package/template/neo-h5-cmps/src/components/entityList__c/index.tsx +0 -4
- package/template/neo-h5-cmps/src/components/entityList__c/model.ts +10 -5
- package/template/neo-h5-cmps/src/components/entityTabs__c/index.tsx +29 -17
- package/template/neo-h5-cmps/src/components/entityTabs__c/model.ts +25 -5
- package/template/neo-h5-cmps/src/components/entityTabs__c/style.scss +11 -22
- package/template/neo-h5-cmps/src/components/openChatPageBtn__c/index.tsx +3 -1
- package/template/neo-h5-cmps/src/utils/xobjects.ts +8 -3
- package/template/neo-h5-cmps/tsconfig.json +1 -1
- package/template/neo-order-cmps/package.json +2 -2
- package/template/react-custom-cmp-template/package.json +1 -1
- package/template/react-ts-custom-cmp-template/package.json +1 -1
- package/template/vue2-custom-cmp-template/package.json +1 -1
- package/template/develop/BI /351/241/271/347/233/256/345/210/206/346/236/220/346/212/245/345/221/212.md" +0 -562
- package/template/develop/ChatPage /347/273/204/344/273/266/344/275/277/347/224/250/350/257/264/346/230/216/346/226/207/346/241/243.md" +0 -507
- package/template/develop/EntityGrid Web /347/273/204/344/273/266/347/232/204/350/257/246/347/273/206/345/210/206/346/236/220/346/226/207/346/241/243.md" +0 -868
- package/template/develop/EntityList H5 /347/273/204/344/273/266/347/232/204/350/257/246/347/273/206/345/210/206/346/236/220/346/226/207/346/241/243.md" +0 -1386
- package/template/develop/GlobalSearch/347/273/204/344/273/266/345/257/271/346/257/224/345/210/206/346/236/220.md +0 -866
- package/template/develop/Neo /344/270/255/345/217/257/347/224/250 amis /347/273/204/344/273/266.md" +0 -1490
- package/template/develop/cmpEventFunctions.ts +0 -257
- package/template/develop/cmpEvents.ts +0 -864
- package/template/develop/comTree/347/224/237/346/210/220/350/277/207/347/250/213/345/210/206/346/236/220.md +0 -469
- package/template/develop/commonModules.js +0 -55
- package/template/develop/components-table.md +0 -50
- package/template/develop/neo-custom-cmp-template/README.md +0 -48
- package/template/develop/neo-custom-cmp-template/docs/README.md +0 -13
- package/template/develop/neo-custom-cmp-template/neo.config.js +0 -121
- package/template/develop/neo-custom-cmp-template/package.json +0 -63
- package/template/develop/neo-custom-cmp-template/src/components/contactCardList/README.md +0 -65
- package/template/develop/neo-custom-cmp-template/src/components/contactCardList/index.tsx +0 -180
- package/template/develop/neo-custom-cmp-template/src/components/contactCardList/model.ts +0 -50
- package/template/develop/neo-custom-cmp-template/src/components/contactCardList/style.scss +0 -260
- package/template/develop/neo-custom-cmp-template/src/components/contactForm/README.md +0 -94
- package/template/develop/neo-custom-cmp-template/src/components/contactForm/index.tsx +0 -252
- package/template/develop/neo-custom-cmp-template/src/components/contactForm/model.ts +0 -56
- package/template/develop/neo-custom-cmp-template/src/components/contactForm/style.scss +0 -120
- package/template/develop/neo-custom-cmp-template/src/components/neoEntityGrid/README.md +0 -115
- package/template/develop/neo-custom-cmp-template/src/components/neoEntityGrid/index.tsx +0 -304
- package/template/develop/neo-custom-cmp-template/src/components/neoEntityGrid/model.ts +0 -87
- package/template/develop/neo-custom-cmp-template/src/components/neoEntityGrid/style.scss +0 -127
- package/template/develop/neo-custom-cmp-template/src/utils/axiosFetcher.ts +0 -29
- package/template/develop/neo-custom-cmp-template/src/utils/queryObjectData.ts +0 -39
- package/template/develop/neo-custom-cmp-template/src/utils/xobjects.ts +0 -203
- package/template/develop/neo-custom-cmp-template/tsconfig.json +0 -68
- package/template/develop/neo-ui-component-h5.md +0 -105
- package/template/develop/neo-ui-component-web-xregister.md +0 -31
- package/template/develop/neo-ui-component-web.md +0 -292
- package/template/develop/neoCmps.ts +0 -7508
- package/template/develop/neoGlobalSearchInput /347/273/204/344/273/266/347/232/204/350/257/246/347/273/206/345/210/206/346/236/220/346/226/207/346/241/243.md" +0 -497
- package/template/develop/pageSchema1.json +0 -744
- package/template/develop//344/272/213/344/273/266/345/212/250/344/275/234/344/277/235/345/255/230/346/265/201/347/250/213/345/210/206/346/236/220.md +0 -390
- package/template/develop//345/215/225/345/205/203/346/265/213/350/257/225/344/275/277/347/224/250/350/257/264/346/230/216.md +0 -1139
- package/template/neo-bi-cmps/.prettierrc.js +0 -12
- package/template/neo-bi-cmps/commitlint.config.js +0 -59
- package/template/neo-bi-cmps/neo.config.js +0 -124
- package/template/neo-bi-cmps/public/css/base.css +0 -283
- package/template/neo-bi-cmps/public/scripts/app/bluebird.js +0 -6679
- package/template/neo-bi-cmps/public/template.html +0 -13
- package/template/neo-bi-cmps/src/assets/css/common.scss +0 -127
- package/template/neo-bi-cmps/src/assets/css/mixin.scss +0 -47
- package/template/neo-bi-cmps/src/assets/img/NeoCRM.jpg +0 -0
- package/template/neo-bi-cmps/src/assets/img/custom-widget.svg +0 -1
- package/template/neo-bi-cmps/src/assets/img/favicon.png +0 -0
- package/template/neo-bi-cmps/src/assets/img/table.svg +0 -1
- package/template/neo-bi-cmps/src/components/targetNumber__c/README.md +0 -100
- package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/configSchema.ts +0 -253
- package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.scss +0 -76
- package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.tsx +0 -148
- package/template/neo-bi-cmps/src/components/targetNumber__c/index.tsx +0 -440
- package/template/neo-bi-cmps/src/components/targetNumber__c/model.ts +0 -128
- package/template/neo-bi-cmps/src/components/targetNumber__c/style.scss +0 -173
- package/template/neo-h5-cmps/src/components/simpleTable__c/README.md +0 -90
- package/template/neo-h5-cmps/src/components/simpleTable__c/index.tsx +0 -277
- package/template/neo-h5-cmps/src/components/simpleTable__c/model.ts +0 -91
- package/template/neo-h5-cmps/src/components/simpleTable__c/style.scss +0 -116
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/.prettierrc.js +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/@types/neo-ui-common.d.ts +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/commitlint.config.js +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/public/css/base.css +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/public/scripts/app/bluebird.js +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/public/template.html +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/src/assets/css/common.scss +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/src/assets/css/mixin.scss +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/AIBtn.gif +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/src/assets/img/NeoCRM.jpg +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/aiLogo.png +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/card-list.svg +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/contact-form.svg +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/custom-form.svg +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/src/assets/img/custom-widget.svg +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/data-list.svg +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/detail.svg +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/src/assets/img/favicon.png +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/map.svg +0 -0
- /package/template/{neo-bi-cmps → asset-manage-template}/src/assets/img/search.svg +0 -0
- /package/template/{develop/neo-custom-cmp-template → asset-manage-template}/src/assets/img/table.svg +0 -0
|
@@ -1,1139 +0,0 @@
|
|
|
1
|
-
# Jest 使用指南
|
|
2
|
-
|
|
3
|
-
## 目录
|
|
4
|
-
|
|
5
|
-
- [什么是 Jest](#什么是-jest)
|
|
6
|
-
- [快速开始](#快速开始)
|
|
7
|
-
- [Jest 配置](#jest-配置)
|
|
8
|
-
- [基础语法](#基础语法)
|
|
9
|
-
- [测试文件组织](#测试文件组织)
|
|
10
|
-
- [常用断言](#常用断言)
|
|
11
|
-
- [Mock 使用](#mock-使用)
|
|
12
|
-
- [异步测试](#异步测试)
|
|
13
|
-
- [生命周期钩子](#生命周期钩子)
|
|
14
|
-
- [测试覆盖率](#测试覆盖率)
|
|
15
|
-
- [常见问题](#常见问题)
|
|
16
|
-
- [最佳实践](#最佳实践)
|
|
17
|
-
|
|
18
|
-
## 什么是 Jest
|
|
19
|
-
|
|
20
|
-
Jest 是 Facebook 开发的一个 JavaScript 测试框架,具有以下特点:
|
|
21
|
-
|
|
22
|
-
- ✅ **零配置**:开箱即用,无需复杂配置
|
|
23
|
-
- ✅ **快速**:并行运行测试,提高效率
|
|
24
|
-
- ✅ **内置 Mock**:强大的 Mock 功能
|
|
25
|
-
- ✅ **代码覆盖率**:自动生成覆盖率报告
|
|
26
|
-
- ✅ **快照测试**:轻松测试 UI 组件
|
|
27
|
-
|
|
28
|
-
## 快速开始
|
|
29
|
-
|
|
30
|
-
### 安装
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npm install --save-dev jest
|
|
34
|
-
# 或
|
|
35
|
-
yarn add --dev jest
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 运行测试
|
|
39
|
-
|
|
40
|
-
#### 如何启动 Jest
|
|
41
|
-
|
|
42
|
-
Jest 可以通过多种方式启动:
|
|
43
|
-
|
|
44
|
-
**1. 使用 npm/yarn 脚本(推荐)**
|
|
45
|
-
|
|
46
|
-
在 `package.json` 中配置测试脚本:
|
|
47
|
-
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"scripts": {
|
|
51
|
-
"test": "jest",
|
|
52
|
-
"test:watch": "jest --watch",
|
|
53
|
-
"test:coverage": "jest --coverage"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
然后运行:
|
|
59
|
-
```bash
|
|
60
|
-
# 运行所有测试
|
|
61
|
-
npm test
|
|
62
|
-
# 或
|
|
63
|
-
yarn test
|
|
64
|
-
|
|
65
|
-
# 监听模式(文件变化时自动运行)
|
|
66
|
-
npm test -- --watch
|
|
67
|
-
|
|
68
|
-
# 生成覆盖率报告
|
|
69
|
-
npm test -- --coverage
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**2. 直接使用 Jest 命令**
|
|
73
|
-
|
|
74
|
-
如果全局安装了 Jest:
|
|
75
|
-
```bash
|
|
76
|
-
jest
|
|
77
|
-
jest --watch
|
|
78
|
-
jest --coverage
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**3. 使用 npx(无需全局安装)**
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
npx jest
|
|
85
|
-
npx jest --watch
|
|
86
|
-
npx jest --coverage
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**4. 运行特定测试**
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# 运行特定文件
|
|
93
|
-
npm test -- index.test.tsx
|
|
94
|
-
|
|
95
|
-
# 运行匹配模式的文件
|
|
96
|
-
npm test -- --testNamePattern="应该正确初始化"
|
|
97
|
-
|
|
98
|
-
# 运行特定目录
|
|
99
|
-
npm test -- src/components/Button
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**5. 其他常用选项**
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
# 只运行失败的测试
|
|
106
|
-
npm test -- --onlyFailures
|
|
107
|
-
|
|
108
|
-
# 更新快照
|
|
109
|
-
npm test -- --updateSnapshot
|
|
110
|
-
|
|
111
|
-
# 详细输出
|
|
112
|
-
npm test -- --verbose
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Jest 配置
|
|
116
|
-
|
|
117
|
-
### 配置文件位置
|
|
118
|
-
|
|
119
|
-
Jest 支持多种配置方式,按优先级从高到低:
|
|
120
|
-
|
|
121
|
-
1. **`jest.config.js`** 或 **`jest.config.ts`**(推荐)
|
|
122
|
-
- 放在项目根目录
|
|
123
|
-
- 使用 CommonJS 或 ES6 模块语法
|
|
124
|
-
|
|
125
|
-
2. **`package.json`** 中的 `jest` 字段
|
|
126
|
-
```json
|
|
127
|
-
{
|
|
128
|
-
"jest": {
|
|
129
|
-
"testEnvironment": "jsdom"
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
3. **命令行参数**
|
|
135
|
-
```bash
|
|
136
|
-
jest --testEnvironment=jsdom
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### 基础配置选项
|
|
140
|
-
|
|
141
|
-
#### 1. 测试环境 (testEnvironment)
|
|
142
|
-
|
|
143
|
-
指定测试运行的环境:
|
|
144
|
-
|
|
145
|
-
```javascript
|
|
146
|
-
module.exports = {
|
|
147
|
-
// 'node' - Node.js 环境(默认)
|
|
148
|
-
// 'jsdom' - 浏览器环境(用于测试 React 组件)
|
|
149
|
-
testEnvironment: 'jsdom'
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
#### 2. 测试文件匹配 (testMatch)
|
|
154
|
-
|
|
155
|
-
指定哪些文件被视为测试文件:
|
|
156
|
-
|
|
157
|
-
```javascript
|
|
158
|
-
module.exports = {
|
|
159
|
-
// 默认值
|
|
160
|
-
testMatch: [
|
|
161
|
-
'**/__tests__/**/*.[jt]s?(x)',
|
|
162
|
-
'**/?(*.)+(spec|test).[jt]s?(x)'
|
|
163
|
-
],
|
|
164
|
-
|
|
165
|
-
// 自定义:只匹配 __test__ 目录下的文件
|
|
166
|
-
testMatch: ['**/__test__/**/*.test.(ts|tsx)']
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
#### 3. 搜索根目录 (roots)
|
|
171
|
-
|
|
172
|
-
指定 Jest 搜索测试文件的根目录:
|
|
173
|
-
|
|
174
|
-
```javascript
|
|
175
|
-
module.exports = {
|
|
176
|
-
roots: ['<rootDir>/src', '<rootDir>/__test__']
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
#### 4. 模块路径映射 (moduleNameMapper)
|
|
181
|
-
|
|
182
|
-
配置路径别名,类似于 webpack 的 alias:
|
|
183
|
-
|
|
184
|
-
```javascript
|
|
185
|
-
module.exports = {
|
|
186
|
-
moduleNameMapper: {
|
|
187
|
-
// 路径别名
|
|
188
|
-
'^@components/(.*)$': '<rootDir>/src/components/$1',
|
|
189
|
-
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
|
|
190
|
-
|
|
191
|
-
// Mock 静态资源
|
|
192
|
-
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
|
193
|
-
'\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js'
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
#### 5. 模块目录 (moduleDirectories)
|
|
199
|
-
|
|
200
|
-
指定模块解析的目录:
|
|
201
|
-
|
|
202
|
-
```javascript
|
|
203
|
-
module.exports = {
|
|
204
|
-
moduleDirectories: ['node_modules', 'src']
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### TypeScript 项目配置
|
|
209
|
-
|
|
210
|
-
#### 使用 ts-jest preset(推荐)
|
|
211
|
-
|
|
212
|
-
```javascript
|
|
213
|
-
module.exports = {
|
|
214
|
-
preset: 'ts-jest',
|
|
215
|
-
testEnvironment: 'jsdom'
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
#### 自定义 TypeScript 转换
|
|
220
|
-
|
|
221
|
-
```javascript
|
|
222
|
-
module.exports = {
|
|
223
|
-
transform: {
|
|
224
|
-
'^.+\\.(ts|tsx)$': [
|
|
225
|
-
'ts-jest',
|
|
226
|
-
{
|
|
227
|
-
// ts-jest 配置选项
|
|
228
|
-
diagnostics: false, // 禁用类型检查(加快速度)
|
|
229
|
-
isolatedModules: true, // 启用隔离模块模式
|
|
230
|
-
tsconfig: {
|
|
231
|
-
// TypeScript 编译选项
|
|
232
|
-
target: 'ES2019',
|
|
233
|
-
module: 'commonjs',
|
|
234
|
-
jsx: 'react',
|
|
235
|
-
esModuleInterop: true,
|
|
236
|
-
experimentalDecorators: true // 支持装饰器
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
]
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### React 项目配置
|
|
245
|
-
|
|
246
|
-
#### 完整配置示例
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
module.exports = {
|
|
250
|
-
// 测试环境
|
|
251
|
-
testEnvironment: 'jsdom',
|
|
252
|
-
|
|
253
|
-
// 详细输出
|
|
254
|
-
verbose: true,
|
|
255
|
-
|
|
256
|
-
// 搜索根目录
|
|
257
|
-
roots: ['<rootDir>/src'],
|
|
258
|
-
|
|
259
|
-
// TypeScript 预设
|
|
260
|
-
preset: 'ts-jest',
|
|
261
|
-
|
|
262
|
-
// 转换配置
|
|
263
|
-
transform: {
|
|
264
|
-
'^.+\\.(ts|tsx)$': ['ts-jest', { diagnostics: false }],
|
|
265
|
-
'^.+\\.(js|jsx)$': 'babel-jest'
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
// 忽略转换的模块
|
|
269
|
-
transformIgnorePatterns: [
|
|
270
|
-
'node_modules/(?!(uuid|axios)/)' // 不忽略 uuid 和 axios
|
|
271
|
-
],
|
|
272
|
-
|
|
273
|
-
// 模块路径映射
|
|
274
|
-
moduleNameMapper: {
|
|
275
|
-
'^@/(.*)$': '<rootDir>/src/$1',
|
|
276
|
-
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
|
277
|
-
'\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js'
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
// 测试文件匹配
|
|
281
|
-
testMatch: ['**/__test__/**/*.test.(ts|tsx)'],
|
|
282
|
-
|
|
283
|
-
// 模块文件扩展名
|
|
284
|
-
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
285
|
-
|
|
286
|
-
// 测试前执行的脚本
|
|
287
|
-
setupFilesAfterEnv: ['<rootDir>/__test__/setupTests.ts']
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### 全局设置文件 (setupFilesAfterEnv)
|
|
292
|
-
|
|
293
|
-
在每个测试文件运行前执行的脚本,用于全局配置:
|
|
294
|
-
|
|
295
|
-
```javascript
|
|
296
|
-
// jest.config.js
|
|
297
|
-
module.exports = {
|
|
298
|
-
setupFilesAfterEnv: ['<rootDir>/__test__/setupTests.ts']
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
// __test__/setupTests.ts
|
|
304
|
-
import '@testing-library/jest-dom'
|
|
305
|
-
|
|
306
|
-
// Mock ResizeObserver
|
|
307
|
-
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
308
|
-
observe: jest.fn(),
|
|
309
|
-
unobserve: jest.fn(),
|
|
310
|
-
disconnect: jest.fn()
|
|
311
|
-
}))
|
|
312
|
-
|
|
313
|
-
// Mock window.matchMedia
|
|
314
|
-
Object.defineProperty(window, 'matchMedia', {
|
|
315
|
-
writable: true,
|
|
316
|
-
value: jest.fn().mockImplementation(query => ({
|
|
317
|
-
matches: false,
|
|
318
|
-
media: query,
|
|
319
|
-
onchange: null,
|
|
320
|
-
addListener: jest.fn(),
|
|
321
|
-
removeListener: jest.fn(),
|
|
322
|
-
addEventListener: jest.fn(),
|
|
323
|
-
removeEventListener: jest.fn(),
|
|
324
|
-
dispatchEvent: jest.fn()
|
|
325
|
-
}))
|
|
326
|
-
})
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### 覆盖率配置
|
|
330
|
-
|
|
331
|
-
#### 收集覆盖率
|
|
332
|
-
|
|
333
|
-
```javascript
|
|
334
|
-
module.exports = {
|
|
335
|
-
// 收集覆盖率的文件范围
|
|
336
|
-
collectCoverageFrom: [
|
|
337
|
-
'src/**/*.{ts,tsx}',
|
|
338
|
-
'!src/**/*.d.ts', // 排除类型定义文件
|
|
339
|
-
'!src/**/*.test.{ts,tsx}', // 排除测试文件
|
|
340
|
-
'!src/**/*.stories.{ts,tsx}', // 排除 Storybook 文件
|
|
341
|
-
'!src/index.tsx' // 排除入口文件
|
|
342
|
-
],
|
|
343
|
-
|
|
344
|
-
// 覆盖率阈值
|
|
345
|
-
coverageThreshold: {
|
|
346
|
-
global: {
|
|
347
|
-
branches: 75, // 分支覆盖率至少 75%
|
|
348
|
-
functions: 85, // 函数覆盖率至少 85%
|
|
349
|
-
lines: 85, // 行覆盖率至少 85%
|
|
350
|
-
statements: 85 // 语句覆盖率至少 85%
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
|
|
354
|
-
// 覆盖率报告格式
|
|
355
|
-
coverageReporters: ['text', 'lcov', 'html']
|
|
356
|
-
}
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### 其他常用配置
|
|
360
|
-
|
|
361
|
-
#### 1. 全局变量 (globals)
|
|
362
|
-
|
|
363
|
-
```javascript
|
|
364
|
-
module.exports = {
|
|
365
|
-
globals: {
|
|
366
|
-
__DEV__: true,
|
|
367
|
-
__TEST__: true
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
#### 2. 测试超时时间 (testTimeout)
|
|
373
|
-
|
|
374
|
-
```javascript
|
|
375
|
-
module.exports = {
|
|
376
|
-
testTimeout: 10000 // 10 秒(默认 5 秒)
|
|
377
|
-
}
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
#### 3. 最大并发数 (maxWorkers)
|
|
381
|
-
|
|
382
|
-
```javascript
|
|
383
|
-
module.exports = {
|
|
384
|
-
maxWorkers: '50%' // 使用 50% 的 CPU 核心
|
|
385
|
-
// 或指定具体数量
|
|
386
|
-
// maxWorkers: 4
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
#### 4. 清除 Mock (clearMocks)
|
|
391
|
-
|
|
392
|
-
```javascript
|
|
393
|
-
module.exports = {
|
|
394
|
-
clearMocks: true, // 每个测试前自动清除 Mock
|
|
395
|
-
resetMocks: true, // 每个测试前自动重置 Mock
|
|
396
|
-
restoreMocks: true // 每个测试前自动恢复原始实现
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
#### 5. 快照序列化器 (snapshotSerializers)
|
|
401
|
-
|
|
402
|
-
```javascript
|
|
403
|
-
module.exports = {
|
|
404
|
-
snapshotSerializers: ['enzyme-to-json/serializer']
|
|
405
|
-
}
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
### 实际项目配置示例
|
|
409
|
-
|
|
410
|
-
基于本项目的实际配置:
|
|
411
|
-
|
|
412
|
-
```javascript
|
|
413
|
-
// jest.config.js
|
|
414
|
-
module.exports = {
|
|
415
|
-
// 测试环境:浏览器环境(用于测试 React 组件)
|
|
416
|
-
testEnvironment: 'jsdom',
|
|
417
|
-
|
|
418
|
-
// 详细输出模式
|
|
419
|
-
verbose: true,
|
|
420
|
-
|
|
421
|
-
// 搜索根目录
|
|
422
|
-
roots: ['<rootDir>/src', '<rootDir>/__test__'],
|
|
423
|
-
|
|
424
|
-
// TypeScript 预设
|
|
425
|
-
preset: 'ts-jest',
|
|
426
|
-
|
|
427
|
-
// 转换配置
|
|
428
|
-
transform: {
|
|
429
|
-
'^.+\\.(ts|tsx)$': [
|
|
430
|
-
'ts-jest',
|
|
431
|
-
{
|
|
432
|
-
diagnostics: false,
|
|
433
|
-
isolatedModules: true,
|
|
434
|
-
tsconfig: {
|
|
435
|
-
target: 'ES2019',
|
|
436
|
-
module: 'commonjs',
|
|
437
|
-
jsx: 'react',
|
|
438
|
-
experimentalDecorators: true,
|
|
439
|
-
emitDecoratorMetadata: true
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
],
|
|
443
|
-
'^.+\\.(js|jsx)$': 'babel-jest'
|
|
444
|
-
},
|
|
445
|
-
|
|
446
|
-
// 忽略转换的模块(某些 ES6 模块需要转换)
|
|
447
|
-
transformIgnorePatterns: ['node_modules/(?!(uuid|axios)/)'],
|
|
448
|
-
|
|
449
|
-
// 模块目录
|
|
450
|
-
moduleDirectories: ['node_modules', 'src'],
|
|
451
|
-
|
|
452
|
-
// 模块路径映射
|
|
453
|
-
moduleNameMapper: {
|
|
454
|
-
// 包别名
|
|
455
|
-
'^neo-ui-common$': '<rootDir>/../neo-ui-common/src',
|
|
456
|
-
'^neo-ui-common/(.*)$': '<rootDir>/../neo-ui-common/src/$1',
|
|
457
|
-
|
|
458
|
-
// 路径别名
|
|
459
|
-
'^@components/(.*)$': '<rootDir>/src/components/$1',
|
|
460
|
-
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
|
|
461
|
-
|
|
462
|
-
// Mock 静态资源
|
|
463
|
-
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
|
464
|
-
'\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js',
|
|
465
|
-
|
|
466
|
-
// Mock 特定模块
|
|
467
|
-
'^uuid$': '<rootDir>/src/__mocks__/uuid.js'
|
|
468
|
-
},
|
|
469
|
-
|
|
470
|
-
// 测试前执行的脚本
|
|
471
|
-
setupFilesAfterEnv: ['<rootDir>/__test__/setupTests.ts'],
|
|
472
|
-
|
|
473
|
-
// 测试文件匹配规则
|
|
474
|
-
testMatch: ['**/__test__/**/*.test.(ts|tsx)'],
|
|
475
|
-
|
|
476
|
-
// 模块文件扩展名
|
|
477
|
-
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
478
|
-
|
|
479
|
-
// 覆盖率配置
|
|
480
|
-
collectCoverageFrom: [
|
|
481
|
-
'src/**/*.{ts,tsx}',
|
|
482
|
-
'!src/**/*.d.ts',
|
|
483
|
-
'!src/**/*.test.{ts,tsx}',
|
|
484
|
-
'!src/**/*.stories.{ts,tsx}',
|
|
485
|
-
'!src/index.tsx'
|
|
486
|
-
],
|
|
487
|
-
|
|
488
|
-
// 覆盖率阈值
|
|
489
|
-
coverageThreshold: {
|
|
490
|
-
global: {
|
|
491
|
-
branches: 75,
|
|
492
|
-
functions: 85,
|
|
493
|
-
lines: 85,
|
|
494
|
-
statements: 85
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### 配置验证
|
|
501
|
-
|
|
502
|
-
运行以下命令验证配置是否正确:
|
|
503
|
-
|
|
504
|
-
```bash
|
|
505
|
-
# 显示 Jest 配置信息
|
|
506
|
-
npx jest --showConfig
|
|
507
|
-
|
|
508
|
-
# 列出所有匹配的测试文件(不运行)
|
|
509
|
-
npx jest --listTests
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
### 配置最佳实践
|
|
513
|
-
|
|
514
|
-
1. **使用 `jest.config.js`**:将配置放在独立的配置文件中,便于管理
|
|
515
|
-
2. **合理设置 `testMatch`**:明确指定测试文件的匹配规则
|
|
516
|
-
3. **配置路径别名**:使用 `moduleNameMapper` 简化导入路径
|
|
517
|
-
4. **Mock 静态资源**:避免在测试中处理图片、样式等静态资源
|
|
518
|
-
5. **设置覆盖率阈值**:确保代码质量
|
|
519
|
-
6. **使用 `setupFilesAfterEnv`**:统一全局测试环境配置
|
|
520
|
-
|
|
521
|
-
## 基础语法
|
|
522
|
-
|
|
523
|
-
### 最简单的测试
|
|
524
|
-
|
|
525
|
-
```javascript
|
|
526
|
-
// math.test.js
|
|
527
|
-
function add(a, b) {
|
|
528
|
-
return a + b
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
test('1 + 2 应该等于 3', () => {
|
|
532
|
-
expect(add(1, 2)).toBe(3)
|
|
533
|
-
})
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
### 使用 describe 分组
|
|
537
|
-
|
|
538
|
-
```javascript
|
|
539
|
-
describe('计算器功能', () => {
|
|
540
|
-
test('加法测试', () => {
|
|
541
|
-
expect(add(1, 2)).toBe(3)
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
test('减法测试', () => {
|
|
545
|
-
expect(subtract(5, 2)).toBe(3)
|
|
546
|
-
})
|
|
547
|
-
})
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
### test 和 it 的区别
|
|
551
|
-
|
|
552
|
-
`test` 和 `it` 是完全一样的,只是写法不同:
|
|
553
|
-
|
|
554
|
-
```javascript
|
|
555
|
-
// 两种写法等价
|
|
556
|
-
test('测试名称', () => {})
|
|
557
|
-
it('测试名称', () => {})
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
## 测试文件组织
|
|
561
|
-
|
|
562
|
-
### Jest 如何找到单元测试文件?
|
|
563
|
-
|
|
564
|
-
Jest 通过以下方式自动发现和运行测试文件:
|
|
565
|
-
|
|
566
|
-
**1. 默认搜索规则**
|
|
567
|
-
|
|
568
|
-
Jest 会按照以下规则查找测试文件:
|
|
569
|
-
- 文件名包含 `.test.` 或 `.spec.` 的文件
|
|
570
|
-
- 文件扩展名:`.js`、`.jsx`、`.ts`、`.tsx`
|
|
571
|
-
- 默认搜索目录:项目根目录下的所有文件
|
|
572
|
-
|
|
573
|
-
**2. 配置文件中的 `testMatch`**
|
|
574
|
-
|
|
575
|
-
在 `jest.config.js` 中可以自定义测试文件匹配规则:
|
|
576
|
-
|
|
577
|
-
```javascript
|
|
578
|
-
module.exports = {
|
|
579
|
-
// 自定义测试文件匹配模式
|
|
580
|
-
testMatch: [
|
|
581
|
-
'**/__test__/**/*.test.(ts|tsx)', // __test__ 目录下的 .test.ts 或 .test.tsx 文件
|
|
582
|
-
'**/?(*.)+(spec|test).(ts|tsx)' // 任何位置的 .spec.ts 或 .test.ts 文件
|
|
583
|
-
]
|
|
584
|
-
}
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
**3. 配置文件中的 `roots`**
|
|
588
|
-
|
|
589
|
-
指定 Jest 搜索的根目录:
|
|
590
|
-
|
|
591
|
-
```javascript
|
|
592
|
-
module.exports = {
|
|
593
|
-
roots: ['<rootDir>/src', '<rootDir>/__test__']
|
|
594
|
-
// Jest 只会在 src 和 __test__ 目录下搜索测试文件
|
|
595
|
-
}
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
**4. 搜索优先级**
|
|
599
|
-
|
|
600
|
-
Jest 会按照以下顺序查找:
|
|
601
|
-
1. 首先检查 `testMatch` 配置
|
|
602
|
-
2. 如果没有配置,使用默认规则:`**/__tests__/**/*.[jt]s?(x)` 和 `**/?(*.)+(spec|test).[jt]s?(x)`
|
|
603
|
-
3. 在 `roots` 指定的目录中搜索(如果配置了的话)
|
|
604
|
-
|
|
605
|
-
**5. 实际示例**
|
|
606
|
-
|
|
607
|
-
假设项目结构如下:
|
|
608
|
-
```
|
|
609
|
-
project/
|
|
610
|
-
├── src/
|
|
611
|
-
│ ├── components/
|
|
612
|
-
│ │ ├── Button/
|
|
613
|
-
│ │ │ ├── index.tsx
|
|
614
|
-
│ │ │ └── __test__/
|
|
615
|
-
│ │ │ └── index.test.tsx ✅ 会被找到
|
|
616
|
-
│ │ └── Input.tsx
|
|
617
|
-
│ └── utils.test.tsx ✅ 会被找到
|
|
618
|
-
└── __test__/
|
|
619
|
-
└── integration.test.tsx ✅ 会被找到
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
如果配置了 `testMatch: ['**/__test__/**/*.test.(ts|tsx)']`,则只有 `__test__` 目录下的测试文件会被找到。
|
|
623
|
-
|
|
624
|
-
### 文件命名
|
|
625
|
-
|
|
626
|
-
- 测试文件以 `.test.js`、`.test.ts`、`.test.tsx` 结尾
|
|
627
|
-
- 或者以 `.spec.js`、`.spec.ts`、`.spec.tsx` 结尾
|
|
628
|
-
|
|
629
|
-
### 目录结构
|
|
630
|
-
|
|
631
|
-
推荐的项目结构:
|
|
632
|
-
|
|
633
|
-
```
|
|
634
|
-
src/
|
|
635
|
-
├── components/
|
|
636
|
-
│ ├── Button/
|
|
637
|
-
│ │ ├── index.tsx
|
|
638
|
-
│ │ └── __test__/
|
|
639
|
-
│ │ └── index.test.tsx
|
|
640
|
-
│ └── Input/
|
|
641
|
-
│ ├── index.tsx
|
|
642
|
-
│ └── index.test.tsx
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
**注意:**
|
|
646
|
-
- 测试文件可以放在 `__test__` 目录下(推荐,便于管理)
|
|
647
|
-
- 也可以放在与被测试文件相同的目录下(如 `Button.test.tsx` 与 `Button.tsx` 同级)
|
|
648
|
-
- 根据项目配置的 `testMatch` 规则选择合适的命名和位置
|
|
649
|
-
|
|
650
|
-
## 常用断言
|
|
651
|
-
|
|
652
|
-
### 基础断言
|
|
653
|
-
|
|
654
|
-
```javascript
|
|
655
|
-
// 相等性判断
|
|
656
|
-
expect(1 + 1).toBe(2) // 严格相等 ===
|
|
657
|
-
expect({a: 1}).toEqual({a: 1}) // 对象值相等
|
|
658
|
-
expect({a: 1}).toStrictEqual({a: 1}) // 严格相等(推荐)
|
|
659
|
-
|
|
660
|
-
// 真值判断
|
|
661
|
-
expect(true).toBeTruthy()
|
|
662
|
-
expect(false).toBeFalsy()
|
|
663
|
-
expect(null).toBeNull()
|
|
664
|
-
expect(undefined).toBeUndefined()
|
|
665
|
-
|
|
666
|
-
// 数字比较
|
|
667
|
-
expect(2).toBeGreaterThan(1)
|
|
668
|
-
expect(1).toBeLessThan(2)
|
|
669
|
-
expect(0.1 + 0.2).toBeCloseTo(0.3) // 浮点数比较
|
|
670
|
-
|
|
671
|
-
// 字符串匹配
|
|
672
|
-
expect('hello').toMatch(/llo/)
|
|
673
|
-
expect('hello').toContain('ell')
|
|
674
|
-
|
|
675
|
-
// 数组/对象包含
|
|
676
|
-
expect([1, 2, 3]).toContain(2)
|
|
677
|
-
expect({a: 1, b: 2}).toHaveProperty('a')
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
### 函数调用断言
|
|
681
|
-
|
|
682
|
-
```javascript
|
|
683
|
-
const mockFn = jest.fn()
|
|
684
|
-
|
|
685
|
-
mockFn('参数1', '参数2')
|
|
686
|
-
|
|
687
|
-
// 检查是否被调用
|
|
688
|
-
expect(mockFn).toHaveBeenCalled()
|
|
689
|
-
|
|
690
|
-
// 检查调用次数
|
|
691
|
-
expect(mockFn).toHaveBeenCalledTimes(1)
|
|
692
|
-
|
|
693
|
-
// 检查调用参数
|
|
694
|
-
expect(mockFn).toHaveBeenCalledWith('参数1', '参数2')
|
|
695
|
-
|
|
696
|
-
// 检查最后一次调用
|
|
697
|
-
expect(mockFn).toHaveBeenLastCalledWith('参数1', '参数2')
|
|
698
|
-
|
|
699
|
-
// 检查返回值
|
|
700
|
-
expect(mockFn).toHaveReturned()
|
|
701
|
-
expect(mockFn).toHaveReturnedWith('返回值')
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
### 异常断言
|
|
705
|
-
|
|
706
|
-
```javascript
|
|
707
|
-
// 应该抛出异常
|
|
708
|
-
expect(() => {
|
|
709
|
-
throw new Error('错误')
|
|
710
|
-
}).toThrow()
|
|
711
|
-
|
|
712
|
-
// 应该抛出特定错误
|
|
713
|
-
expect(() => {
|
|
714
|
-
throw new Error('错误')
|
|
715
|
-
}).toThrow('错误')
|
|
716
|
-
|
|
717
|
-
// 不应该抛出异常
|
|
718
|
-
expect(() => {
|
|
719
|
-
normalFunction()
|
|
720
|
-
}).not.toThrow()
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
## Mock 使用
|
|
724
|
-
|
|
725
|
-
### 什么是 Mock?
|
|
726
|
-
|
|
727
|
-
**Mock(模拟)** 是测试中的一个重要概念,用于创建虚假的依赖对象或函数,以便在测试中:
|
|
728
|
-
|
|
729
|
-
1. **隔离测试单元**:避免测试代码受到外部依赖(如 API、数据库、文件系统等)的影响
|
|
730
|
-
2. **控制测试环境**:模拟各种场景(成功、失败、边界情况)而不需要真实的外部服务
|
|
731
|
-
3. **提高测试速度**:避免真实的网络请求、数据库操作等耗时操作
|
|
732
|
-
4. **增强测试稳定性**:不依赖外部服务的可用性和状态
|
|
733
|
-
|
|
734
|
-
**常见使用场景:**
|
|
735
|
-
- Mock API 请求:避免在测试中发送真实的 HTTP 请求
|
|
736
|
-
- Mock 模块依赖:替换难以测试的第三方模块
|
|
737
|
-
- Mock 浏览器 API:模拟 `window`、`document`、`localStorage` 等浏览器对象
|
|
738
|
-
- Mock 函数调用:验证函数是否被正确调用,以及调用参数和次数
|
|
739
|
-
|
|
740
|
-
**示例说明:**
|
|
741
|
-
|
|
742
|
-
假设你要测试一个函数,它需要调用 API 获取用户数据:
|
|
743
|
-
|
|
744
|
-
```javascript
|
|
745
|
-
// 不使用 Mock:需要真实的 API 服务,测试慢且不稳定
|
|
746
|
-
async function getUserData() {
|
|
747
|
-
const response = await fetch('/api/user') // 真实请求
|
|
748
|
-
return response.json()
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// 使用 Mock:快速、稳定、可控
|
|
752
|
-
jest.mock('fetch', () => jest.fn())
|
|
753
|
-
const mockFetch = require('fetch')
|
|
754
|
-
mockFetch.mockResolvedValue({ data: { name: '测试用户' } })
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
### 基础 Mock
|
|
758
|
-
|
|
759
|
-
```javascript
|
|
760
|
-
// 创建一个 Mock 函数
|
|
761
|
-
const mockFn = jest.fn()
|
|
762
|
-
|
|
763
|
-
// 设置返回值
|
|
764
|
-
mockFn.mockReturnValue(42)
|
|
765
|
-
expect(mockFn()).toBe(42)
|
|
766
|
-
|
|
767
|
-
// 设置一次返回值
|
|
768
|
-
mockFn.mockReturnValueOnce(1)
|
|
769
|
-
mockFn.mockReturnValueOnce(2)
|
|
770
|
-
expect(mockFn()).toBe(1)
|
|
771
|
-
expect(mockFn()).toBe(2)
|
|
772
|
-
|
|
773
|
-
// 模拟实现
|
|
774
|
-
mockFn.mockImplementation((x) => x * 2)
|
|
775
|
-
expect(mockFn(5)).toBe(10)
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
### Mock 模块
|
|
779
|
-
|
|
780
|
-
```javascript
|
|
781
|
-
// 完全 Mock 一个模块
|
|
782
|
-
jest.mock('./api', () => ({
|
|
783
|
-
fetchData: jest.fn(() => Promise.resolve({data: 'test'}))
|
|
784
|
-
}))
|
|
785
|
-
|
|
786
|
-
// 部分 Mock(保留部分原功能)
|
|
787
|
-
jest.mock('./utils', () => ({
|
|
788
|
-
...jest.requireActual('./utils'),
|
|
789
|
-
specificFunction: jest.fn()
|
|
790
|
-
}))
|
|
791
|
-
```
|
|
792
|
-
|
|
793
|
-
### Mock 外部依赖
|
|
794
|
-
|
|
795
|
-
```javascript
|
|
796
|
-
// Mock axios
|
|
797
|
-
jest.mock('axios', () => ({
|
|
798
|
-
get: jest.fn(() => Promise.resolve({data: {}})),
|
|
799
|
-
post: jest.fn(() => Promise.resolve({data: {}}))
|
|
800
|
-
}))
|
|
801
|
-
|
|
802
|
-
// Mock React 组件
|
|
803
|
-
jest.mock('../Button', () => {
|
|
804
|
-
return function MockButton({children}) {
|
|
805
|
-
return <div data-testid="mock-button">{children}</div>
|
|
806
|
-
}
|
|
807
|
-
})
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
### 清理 Mock
|
|
811
|
-
|
|
812
|
-
```javascript
|
|
813
|
-
beforeEach(() => {
|
|
814
|
-
// 清理所有 Mock 的调用记录
|
|
815
|
-
jest.clearAllMocks()
|
|
816
|
-
|
|
817
|
-
// 重置 Mock 实现
|
|
818
|
-
jest.resetAllMocks()
|
|
819
|
-
|
|
820
|
-
// 恢复原始实现
|
|
821
|
-
jest.restoreAllMocks()
|
|
822
|
-
})
|
|
823
|
-
```
|
|
824
|
-
|
|
825
|
-
## 异步测试
|
|
826
|
-
|
|
827
|
-
### Promise 测试
|
|
828
|
-
|
|
829
|
-
```javascript
|
|
830
|
-
// 使用 async/await(推荐)
|
|
831
|
-
test('异步获取数据', async () => {
|
|
832
|
-
const data = await fetchData()
|
|
833
|
-
expect(data).toEqual({id: 1})
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
// 使用 .then()
|
|
837
|
-
test('异步获取数据', () => {
|
|
838
|
-
return fetchData().then(data => {
|
|
839
|
-
expect(data).toEqual({id: 1})
|
|
840
|
-
})
|
|
841
|
-
})
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
### 错误处理测试
|
|
845
|
-
|
|
846
|
-
```javascript
|
|
847
|
-
test('处理异步错误', async () => {
|
|
848
|
-
await expect(fetchData()).rejects.toThrow('网络错误')
|
|
849
|
-
|
|
850
|
-
// 或使用 try/catch
|
|
851
|
-
try {
|
|
852
|
-
await fetchData()
|
|
853
|
-
} catch (error) {
|
|
854
|
-
expect(error.message).toBe('网络错误')
|
|
855
|
-
}
|
|
856
|
-
})
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
### 等待 DOM 更新
|
|
860
|
-
|
|
861
|
-
```javascript
|
|
862
|
-
import { waitFor } from '@testing-library/react'
|
|
863
|
-
|
|
864
|
-
test('等待元素出现', async () => {
|
|
865
|
-
render(<Component />)
|
|
866
|
-
|
|
867
|
-
await waitFor(() => {
|
|
868
|
-
expect(screen.getByText('加载完成')).toBeInTheDocument()
|
|
869
|
-
})
|
|
870
|
-
})
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
## 生命周期钩子
|
|
874
|
-
|
|
875
|
-
### 全局钩子
|
|
876
|
-
|
|
877
|
-
```javascript
|
|
878
|
-
// 所有测试前执行一次
|
|
879
|
-
beforeAll(() => {
|
|
880
|
-
// 初始化数据库连接等
|
|
881
|
-
})
|
|
882
|
-
|
|
883
|
-
// 所有测试后执行一次
|
|
884
|
-
afterAll(() => {
|
|
885
|
-
// 清理资源
|
|
886
|
-
})
|
|
887
|
-
```
|
|
888
|
-
|
|
889
|
-
### 每个测试的钩子
|
|
890
|
-
|
|
891
|
-
```javascript
|
|
892
|
-
describe('测试套件', () => {
|
|
893
|
-
// 每个测试前执行
|
|
894
|
-
beforeEach(() => {
|
|
895
|
-
// 重置状态
|
|
896
|
-
jest.clearAllMocks()
|
|
897
|
-
})
|
|
898
|
-
|
|
899
|
-
// 每个测试后执行
|
|
900
|
-
afterEach(() => {
|
|
901
|
-
// 清理 DOM
|
|
902
|
-
cleanup()
|
|
903
|
-
})
|
|
904
|
-
|
|
905
|
-
test('测试1', () => {
|
|
906
|
-
// 测试代码
|
|
907
|
-
})
|
|
908
|
-
|
|
909
|
-
test('测试2', () => {
|
|
910
|
-
// 测试代码
|
|
911
|
-
})
|
|
912
|
-
})
|
|
913
|
-
```
|
|
914
|
-
|
|
915
|
-
## 测试覆盖率
|
|
916
|
-
|
|
917
|
-
### 查看覆盖率
|
|
918
|
-
|
|
919
|
-
```bash
|
|
920
|
-
npm test -- --coverage
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
### 覆盖率指标
|
|
924
|
-
|
|
925
|
-
- **语句覆盖率**:代码中有多少语句被执行了
|
|
926
|
-
- **分支覆盖率**:有多少 if/else 分支被执行了
|
|
927
|
-
- **函数覆盖率**:有多少函数被调用了
|
|
928
|
-
- **行覆盖率**:有多少行代码被执行了
|
|
929
|
-
|
|
930
|
-
### 覆盖率配置
|
|
931
|
-
|
|
932
|
-
在 `package.json` 中配置:
|
|
933
|
-
|
|
934
|
-
```json
|
|
935
|
-
{
|
|
936
|
-
"jest": {
|
|
937
|
-
"collectCoverageFrom": [
|
|
938
|
-
"src/**/*.{js,jsx,ts,tsx}",
|
|
939
|
-
"!src/**/*.d.ts",
|
|
940
|
-
"!src/**/__test__/**"
|
|
941
|
-
],
|
|
942
|
-
"coverageThreshold": {
|
|
943
|
-
"global": {
|
|
944
|
-
"branches": 80,
|
|
945
|
-
"functions": 80,
|
|
946
|
-
"lines": 80,
|
|
947
|
-
"statements": 80
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
```
|
|
953
|
-
|
|
954
|
-
## 常见问题
|
|
955
|
-
|
|
956
|
-
### 1. 如何测试 React 组件?
|
|
957
|
-
|
|
958
|
-
```javascript
|
|
959
|
-
import { render, screen } from '@testing-library/react'
|
|
960
|
-
import Component from './Component'
|
|
961
|
-
|
|
962
|
-
test('渲染组件', () => {
|
|
963
|
-
render(<Component name="测试" />)
|
|
964
|
-
expect(screen.getByText('测试')).toBeInTheDocument()
|
|
965
|
-
})
|
|
966
|
-
```
|
|
967
|
-
|
|
968
|
-
### 2. 如何测试用户交互?
|
|
969
|
-
|
|
970
|
-
```javascript
|
|
971
|
-
import { fireEvent } from '@testing-library/react'
|
|
972
|
-
|
|
973
|
-
test('点击按钮', () => {
|
|
974
|
-
render(<Button onClick={handleClick}>点击</Button>)
|
|
975
|
-
fireEvent.click(screen.getByText('点击'))
|
|
976
|
-
expect(handleClick).toHaveBeenCalled()
|
|
977
|
-
})
|
|
978
|
-
```
|
|
979
|
-
|
|
980
|
-
### 3. 如何处理定时器?
|
|
981
|
-
|
|
982
|
-
```javascript
|
|
983
|
-
// 使用 Jest 的定时器 Mock
|
|
984
|
-
jest.useFakeTimers()
|
|
985
|
-
|
|
986
|
-
test('定时器测试', () => {
|
|
987
|
-
const callback = jest.fn()
|
|
988
|
-
setTimeout(callback, 1000)
|
|
989
|
-
|
|
990
|
-
jest.advanceTimersByTime(1000)
|
|
991
|
-
expect(callback).toHaveBeenCalled()
|
|
992
|
-
|
|
993
|
-
jest.useRealTimers() // 恢复真实定时器
|
|
994
|
-
})
|
|
995
|
-
```
|
|
996
|
-
|
|
997
|
-
### 4. 如何跳过测试?
|
|
998
|
-
|
|
999
|
-
```javascript
|
|
1000
|
-
// 跳过单个测试
|
|
1001
|
-
test.skip('这个测试被跳过', () => {})
|
|
1002
|
-
|
|
1003
|
-
// 跳过整个测试套件
|
|
1004
|
-
describe.skip('这个套件被跳过', () => {})
|
|
1005
|
-
|
|
1006
|
-
// 条件跳过
|
|
1007
|
-
test('条件测试', () => {
|
|
1008
|
-
if (process.env.NODE_ENV === 'production') {
|
|
1009
|
-
return // 跳过
|
|
1010
|
-
}
|
|
1011
|
-
// 测试代码
|
|
1012
|
-
})
|
|
1013
|
-
```
|
|
1014
|
-
|
|
1015
|
-
### 5. 如何只运行特定测试?
|
|
1016
|
-
|
|
1017
|
-
```javascript
|
|
1018
|
-
// 使用 test.only 或 it.only
|
|
1019
|
-
test.only('只运行这个测试', () => {})
|
|
1020
|
-
|
|
1021
|
-
// 或使用 describe.only
|
|
1022
|
-
describe.only('只运行这个套件', () => {})
|
|
1023
|
-
```
|
|
1024
|
-
|
|
1025
|
-
## 最佳实践
|
|
1026
|
-
|
|
1027
|
-
### 1. 测试命名要清晰
|
|
1028
|
-
|
|
1029
|
-
```javascript
|
|
1030
|
-
// ✅ 好的命名
|
|
1031
|
-
test('当用户点击删除按钮时应该显示确认对话框', () => {})
|
|
1032
|
-
|
|
1033
|
-
// ❌ 不好的命名
|
|
1034
|
-
test('测试删除', () => {})
|
|
1035
|
-
```
|
|
1036
|
-
|
|
1037
|
-
### 2. 一个测试只测试一件事
|
|
1038
|
-
|
|
1039
|
-
```javascript
|
|
1040
|
-
// ✅ 好的做法
|
|
1041
|
-
test('应该正确计算总和', () => {
|
|
1042
|
-
expect(sum([1, 2, 3])).toBe(6)
|
|
1043
|
-
})
|
|
1044
|
-
|
|
1045
|
-
test('应该处理空数组', () => {
|
|
1046
|
-
expect(sum([])).toBe(0)
|
|
1047
|
-
})
|
|
1048
|
-
|
|
1049
|
-
// ❌ 不好的做法
|
|
1050
|
-
test('测试 sum 函数', () => {
|
|
1051
|
-
expect(sum([1, 2, 3])).toBe(6)
|
|
1052
|
-
expect(sum([])).toBe(0)
|
|
1053
|
-
expect(sum([-1, 1])).toBe(0)
|
|
1054
|
-
})
|
|
1055
|
-
```
|
|
1056
|
-
|
|
1057
|
-
### 3. 使用描述性的断言消息
|
|
1058
|
-
|
|
1059
|
-
```javascript
|
|
1060
|
-
// ✅ 好的做法
|
|
1061
|
-
expect(result).toBe(expected, '计算结果应该等于预期值')
|
|
1062
|
-
|
|
1063
|
-
// ❌ 不好的做法
|
|
1064
|
-
expect(result).toBe(expected)
|
|
1065
|
-
```
|
|
1066
|
-
|
|
1067
|
-
### 4. 保持测试独立
|
|
1068
|
-
|
|
1069
|
-
```javascript
|
|
1070
|
-
// ✅ 好的做法:每个测试独立
|
|
1071
|
-
beforeEach(() => {
|
|
1072
|
-
jest.clearAllMocks()
|
|
1073
|
-
// 重置状态
|
|
1074
|
-
})
|
|
1075
|
-
|
|
1076
|
-
// ❌ 不好的做法:测试之间相互依赖
|
|
1077
|
-
let sharedState = {}
|
|
1078
|
-
```
|
|
1079
|
-
|
|
1080
|
-
### 5. 合理使用 Mock
|
|
1081
|
-
|
|
1082
|
-
```javascript
|
|
1083
|
-
// ✅ 好的做法:只 Mock 外部依赖
|
|
1084
|
-
jest.mock('axios')
|
|
1085
|
-
jest.mock('../api')
|
|
1086
|
-
|
|
1087
|
-
// ❌ 不好的做法:过度 Mock
|
|
1088
|
-
jest.mock('./utils') // 如果 utils 是内部工具,不应该 Mock
|
|
1089
|
-
```
|
|
1090
|
-
|
|
1091
|
-
### 6. 测试边界情况
|
|
1092
|
-
|
|
1093
|
-
```javascript
|
|
1094
|
-
describe('边界情况', () => {
|
|
1095
|
-
test('应该处理空值', () => {
|
|
1096
|
-
expect(process(null)).toBe(null)
|
|
1097
|
-
})
|
|
1098
|
-
|
|
1099
|
-
test('应该处理空数组', () => {
|
|
1100
|
-
expect(process([])).toEqual([])
|
|
1101
|
-
})
|
|
1102
|
-
|
|
1103
|
-
test('应该处理超大数字', () => {
|
|
1104
|
-
expect(process(Number.MAX_SAFE_INTEGER)).toBeDefined()
|
|
1105
|
-
})
|
|
1106
|
-
})
|
|
1107
|
-
```
|
|
1108
|
-
|
|
1109
|
-
### 7. 使用测试工具函数
|
|
1110
|
-
|
|
1111
|
-
```javascript
|
|
1112
|
-
// 创建测试数据的工厂函数
|
|
1113
|
-
const createUser = (overrides = {}) => ({
|
|
1114
|
-
id: 1,
|
|
1115
|
-
name: '测试用户',
|
|
1116
|
-
email: 'test@example.com',
|
|
1117
|
-
...overrides
|
|
1118
|
-
})
|
|
1119
|
-
|
|
1120
|
-
test('测试用户创建', () => {
|
|
1121
|
-
const user = createUser({name: '新用户'})
|
|
1122
|
-
expect(user.name).toBe('新用户')
|
|
1123
|
-
})
|
|
1124
|
-
```
|
|
1125
|
-
|
|
1126
|
-
## 总结
|
|
1127
|
-
|
|
1128
|
-
Jest 是一个功能强大且易于使用的测试框架。记住以下要点:
|
|
1129
|
-
|
|
1130
|
-
1. **写测试要简单**:一个测试只测试一件事
|
|
1131
|
-
2. **命名要清晰**:测试名称要能说明测试的目的
|
|
1132
|
-
3. **保持独立**:测试之间不应该相互依赖
|
|
1133
|
-
4. **合理 Mock**:只 Mock 外部依赖,不要过度 Mock
|
|
1134
|
-
5. **测试边界**:不仅要测试正常情况,也要测试边界和错误情况
|
|
1135
|
-
|
|
1136
|
-
更多详细信息,请参考:
|
|
1137
|
-
- [Jest 官方文档](https://jestjs.io/docs/getting-started)
|
|
1138
|
-
- [React Testing Library](https://testing-library.com/react)
|
|
1139
|
-
- [项目测试规范](.cursor/rules/component-jest.mdc)
|