Vue单元测试 & Jest入门

Jest 是什么?

Jest 是 Facebook 开源的一个测试框架,官网地址:https://facebook.github.io/jest/。Jest 集成了测试运行时、测试断言库、测试覆盖率等测试必要的工具和环境,可以做到开箱即用。Jest 相比 Mocha、Jasmine的优势就是配置少、开箱即用的一个测试框架。

开始使用

  1. 安装Jest 以及一些依赖
1
npm install jest jest-serializer-vue babel-jest vue-jest @vue/test-utils --save-dev
  1. 配置 package.json
1
2
3
4
5
{
"scripts": {
"test": "jest --config test/unit/jest.config.js"
}
}

test 脚本表示:执行 Jest, 且 Jest 的配置文件在 test/unit/jest.config.js 里配置

  1. Jest 配置文件

虽然 Jest 可以零配置使用,但是我们还是要搞清楚这些常用的配置是什么意思,以便可以针对自己的项目进行个性化设置

test/unit/jest.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
38
39
40
41
42
43
44
45
const path = require('path');

/**
* jest 配置
* @see https://facebook.github.io/jest/docs/zh-Hans/configuration.html
*/
module.exports = {
// https://facebook.github.io/jest/docs/zh-Hans/configuration.html#rootdir-string
// 项目根目录,其他路径可以引用 '<rootDir>' 作为基本路径
rootDir: path.resolve(__dirname, '../../'),
// 匹配到的文件将执行测试
testRegex: '/test/.*?\\.(test|spec)\\.jsx?$',
// 类似 webpack 的 resolve.extensions
moduleFileExtensions: [
'js',
'json',
'vue',
],
// 模块名映射,类似 webpack 的 类似 webpack 的 resolve.alias
moduleNameMapper: {
'^src/(.*)$': '<rootDir>/src/$1',
'^@/(.*)$': '<rootDir>/src/components/$1',
},
// 指定相应的模块(提供一个同步方法)处理相应格式的文件
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest',
},
// https://facebook.github.io/jest/docs/zh-Hans/tutorial-react-native.html#snapshot-test
// 快照测试 vue模块
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
// 每次测试前执行,一般用于执行区分其他环境的代码
setupFiles: ['<rootDir>/test/unit/setup'],
// 是否搜集覆盖率信息
collectCoverage: false,
// Jest输出覆盖信息文件的目录
coverageDirectory: '<rootDir>/test/unit/coverage',
// 匹配的文件将收集覆盖率
collectCoverageFrom: [
'src/**/*.{js,vue}',
'!src/main.js',
'!src/router/index.js',
'!**/node_modules/**',
],
};
  1. babel 配置以及依赖安装
1
npm install babel-preset-env babel-preset-stage-2 --save-dev

.babelrc

1
2
3
4
5
6
7
{
"env": {
"test": {
"presets": ["env", "stage-2"]
}
}
}
  1. Eslint 配置

如果在项目中使用了 Eslint, 又想把测试代码加入到 Eslint 的检测范围内,此时发现测试脚本 Eslint 会报错,比如全局变量 jest、’test’、’expect’ 等提示未定义。此时,我们可以把测试脚本的 Eslint 进项个性化设置,在 test/unit 目录下建一个文件 .eslintrc.

test/unit/.eslintrc

1
2
3
4
5
6
7
8
{
"env": {
"jest": true
},
"rules": {
// ...
}
}
  1. 编写测试脚本

1) 先写一个方法 sum.js, 因为我们配置了 babel-jest 处理 js 文件,所以我们可以直接写 ES6 语法:

src/sum.js

1
2
3
export default function(a, b) {
return a + b;
}

2) 编写测试脚本,命名规则为 [name].spec.js 或者 [name].spec.js。我们可以在配置中配置 testRegex 字段来指定 Jest 扫描哪些为测试脚本。所以,我们在新建一个文件 test/unit/specs/sum.spec.js

test/unit/specs/sum.spec.js

1
2
3
4
5
import sum from 'src/sum.js'

test('sum(1, 2) equal 3', () => {
expect(sum(1, 2)).toBe(3);
})

3) 运行测试命令

1
npm test

可以看到输出结果:

Vue 测试

我们可以通过 Vue 官方提供的测试工具 @vue/test-utils 来测试 Vue 文件。

  1. 创建一个 Vue 文件

src/Demo.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<button v-show="show">测试按钮</button>
</div>
</template>

<script>
export default {
data() {
return {
show: false
}
}
}
</script>
  1. 创建 Vue 测试脚本

test/unit/specs/Demo.spec.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { shallowMount } from '@vue/test-utils';
import Demo from 'src/Demo.vue';

describe('Demo.spec.js', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(Demo);
});

test('test show button', () => {
wrapper.setData({ show: true });
expect(wrapper.find('button').isVisible()).toBe(true);
});

test('test hide button', () => {
wrapper.setData({ show: false });
expect(wrapper.find('button').isVisible()).toBe(false);
})
}

解释一下:

  1. shallowMount 是 @vue/test-utils 提供的一个方法,该方法挂载组件并返回一个包裹器对象。包裹器对象暴露了一些方便测试的方法。也可以使用 mount 方法实现相同的功能,此方法与 mount 的不同之处在于,它会保留子组件,只关注当前组件。
  2. describe 是 Jest 的一个全局方法,API 地址,大概意思就是提供一个分组。
  3. beforeEach 是 Jest 的一个全局方法,API 地址,该方法会在每个 test 方法执行前执行。此处的意思是:在每个 test 方法执行前,创建一个全新的包裹器 wrapper
  4. wrapper.setData 方法,是指设置 Vue 的 data 属性值,类似的还有 wrapper.setMethodswrapper.setProps等。
  5. wrapper.find 方法,是指查找此 Vue 组件中,标签名为 button 的第一个元素。类似的还有 wrapper.findAll等。
  6. @vue/test-utils 提供了一系列常用的方法和环境能让我们快速上手 Vue 的单元测试,它里面的功能在本文中只展示了冰山一角,还有一些诸如模拟鼠标和键盘操作的强大方法。想要详细的 API 可以直接去 查看 API

总结

单元测试是在我学习软件工程的时候常常听到的一个词,但是当我开发业务的时候,并没有把单元测试用起来,因为我觉得开发完成之后,还有测试帮我们测,我们不需要花费这么多时间自己测。后来逐渐的开发过程中,我发现一个问题,就是有的公共方法在某些特殊场景下会出现 BUG。所以,我觉得单元测试在我们业务开发的时候还是有必要的。我觉得所有的公共方法,或者公共组件,一定要通过单元测试。而普通的业务逻辑,我倒是觉得没有太多的场景要使用单元测试,一些复杂的场景也可以使用。以上是我自己在开发学习过程中的一些总结和感悟,如有错误之处,还望指出~