使用 Jest 测试前端代码

随着 SPA 的兴起,前端的代码越来越复杂。有时也会根据业务需求写一些通用的前端框架。为了保证代码的健壮性和可维护性,测试就显得尤为重要了。在对比了市面上的前端框架后,决定以 Jest 为例,讨论下前端测试的方向和如何搭建测试环境。

测试的分类

  • 单元测试:测试一个函数或者类中的方法的执行是否符合预期行为,是测试的最小单元。可以与开发同时进行,避免一些简单的逻辑错误。
  • 集成测试:测试系统主要组成模块的功能是否正常,当多个函数或方法组合起来时能否按预期执行。
  • e2e 测试:测试系统在真实的环境中(网络,数据库等)模拟真实的用户场景,对应用各个功能进行完整的黑盒测试。
  • UI 测试:测试界面 UI 是否完全符合设计的要求,包含 UI 的交互和元素的位置和样式。通常可以使用快照(snapshot)进行测试。

编写测试的好处

  1. 单元测试可以在开发时期发现潜在的 BUG,测试数据边界情况。
  2. 改完 BUG 后,可以添加相应的测试做验证。
  3. 重构代码时保证逻辑正确,心里有底。
  4. 测试代码有时可以作为 API 的说明文档,提高合作开发效率。
  5. 在编写测试时,可以对代码有更深入的思考,提高编码水平。

配置 Jest 测试环境

最简单的使用方法仅三步

  1. yarn add jest --dev
  2. 编写测试文件,并命名为 xxx.test.js
  3. npx jest

这篇文章我们以 create-react-app 中集成的 Jest 为例。通过修改测试配置文件,支持测试的拆分和 Puppeteer 的集成,以更好的提升开发体验。

  1. 创建 CRA 项目 create-react-app jest-demo
  2. 弹出 CRA 的配置 yarn eject

这时我们可以看到 Jest 的配置主要都集中在了package.json文件中,初始配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"jest": {
"roots": ["<rootDir>/src"],
"collectCoverageFrom": ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"],
"setupFiles": ["react-app-polyfill/jsdom"],
"setupFilesAfterEnv": [],
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
],
"testEnvironment": "jest-environment-jsdom-fourteen",
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss)$"
],
"modulePaths": [],
"moduleNameMapper": {
"^react-native$": "react-native-web",
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
},
"moduleFileExtensions": [
"web.js",
"js",
"web.ts",
"ts",
"web.tsx",
"tsx",
"json",
"web.jsx",
"jsx",
"node"
],
"watchPlugins": [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname"
]
}
}

testMatch中定义了,查找测试文件的方法,可以看到现在的默认行为是查找__tests__文件夹中的文件和以spec.jstest.js为结尾的文件,要做到测试的拆分,我们只能将配置拆开,定义不同的testMatch
为方便管理配置文件我们将配置文件的公共部分从package.json中移到config/jest中,并命名为base.config.js。完成后删除package.json中的 jest 配置。

base.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module.exports = {
rootDir: process.cwd(),
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'],
setupFiles: ['react-app-polyfill/jsdom'],
setupFilesAfterEnv: [],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)':
'<rootDir>/config/jest/fileTransform.js',
},
transformIgnorePatterns: [
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss)$',
],
modulePaths: [],
moduleNameMapper: {
'^react-native$': 'react-native-web',
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
},
moduleFileExtensions: [
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
'node',
],
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
};

接下来我们拆分单元测试和 e2e 测试,并配置不同的入口。先新建unit.config.js作为单元测试配置的入口。

unit.config.js

1
2
3
4
5
6
const baseConfig = require('./base.config');

module.exports = Object.assign({}, baseConfig, {
testEnvironment: 'jest-environment-jsdom-fourteen',
testMatch: ['<rootDir>/src/**/__tests__/**/*.unit.test.{js,jsx,ts,tsx}'],
});

这样,我们就可以通过__tests__/*.unit.test.js定义单元测试文件了。
下面我们定义 e2e 测试的配置文件,并命名为e2e.config.js
需要安装 Puppeteer 测试环境
yarn add puppeteer jest-puppeteer --dev
并修改 eslint 配置文件,添加 jest-puppeteer 的全局变量

package.json

1
2
3
4
5
6
7
8
9
10
11
{
"eslintConfig": {
"extends": "react-app",
"globals": {
"page": true,
"browser": true,
"context": true,
"jestPuppeteer": true
}
}
}

e2e.config.js

1
2
3
4
5
6
const baseConfig = require('./base.config');

module.exports = Object.assign({}, baseConfig, {
preset: 'jest-puppeteer',
testMatch: ['<rootDir>/e2e/**/*.{js,jsx,ts,tsx}'],
});

最后,我们修改package.json文件,增加测试脚本入口

1
2
3
4
5
6
7
8
{
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test:unit": "node scripts/test.js --config config/jest/unit.config.js",
"test:e2e": "node scripts/test.js --config config/jest/e2e.config.js"
}
}

这样,我们就可以在 e2e 文件夹下编写 e2e 测试,并使用 Puppeteer 环境,具体使用方式可以参考 Github: jest-puppeteer
同时,可以单独编写单元测试文件*.unit.test.js进行单元测试。两者可以独立运行,互不干扰。

  • 单元测试:yarn test:unit
  • e2e 测试:yarn test:e2e

同理,我们还可以尝试编写int.config.js配置文件,拆分集成测试,并通过yarn test:int运行。

最终 config 目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
config
├── env.js
├── jest
│ ├── base.config.js
│ ├── cssTransform.js
│ ├── e2e.config.js
│ ├── fileTransform.js
│ └── unit.config.js
├── modules.js
├── paths.js
├── pnpTs.js
├── webpack.config.js
└── webpackDevServer.config.js
0%