最近在研究单元测试,虽说前端如果不是大且的项目不必要加,但至少得会,因此花了些时间研究,以下是我总结jest的使用。
jest是什么?
Jest是 Facebook 的一套开源的 JavaScript 测试框架, 它自动集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架。它能支持很多框架,比如 Babel、TypeScript、Node、React、Angular、Vue 等诸多框架。
vue中的jest的安装
执行安装命令
vue中使用jest,其实并没有我们想象的那么复杂,之前我使用jest的时候,查了较多资料,也配置了许多设置,繁忙且繁琐,直到最后我才发现,原来仅仅只需要执行一句指令即可。
vue add @vue/cli-plugin-unit-jest
这个命令会帮我们把相关的配置都配好,相关的依赖都装好,还会帮我们生成一个jest.config.js文件。
jest中常用的一些配置项的介绍
module.exports = {
"moduleFileExtensions": [ //不需要配置
"js",
"json",
// 告诉 Jest 处理 `*.vue` 文件
"vue"
],
testMatch: [ //test文件所在位置
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
"transform": { //不需要配置
// 用 `vue-jest` 处理 `*.vue` 文件
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
// 用 `babel-jest` 处理 js
"^.+\\.js$": "babel-jest"
},
"moduleNameMapper": { //不需要配置
"^@/(.*)$": "<rootDir>/src/$1"
},
"collectCoverage": true, //是否创建报告
"collectCoverageFrom": ["**/*.{js,vue}", "!**/node_modules/**"], //创建报告的文件来源
"coverageReporters": ["html", "text-summary"] //报告的格式
"coveragePathIgnorePatterns":"[]" //生成报告需要忽略的文件,默认值为 node_modules
"globals":{ //配置全局变量,此处我配置了一个全局变量VUE_APP_DATA,也可以在setup file中配置,如下说的lodash
"VUE_APP_DATA": {siteENV:'DEV'}}
setupFiles: ['<rootDir>/src/jest-setup.js'] //启动jest需要的文件
};
在项目目录中创建tests文件,再创建unit文件,在其中文件命名的话,就以 xxx.spec.js命名(这个执行命令的时候,已经给我们创建了)
import { shallowMount } from '@vue/test-utils'
import explame from '@/components/Explame'
describe('Explame .vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(Explame , {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
})
在package.json 中添加启动命令,然后通过在控制台执行npm run test:unit ,进行测试
Scripts配置:
"test:unit": "vue-cli-service test:unit"
jest的基本语法规则
Jest 支持三种方式写测试代码
- .spec.js
- .test.js
- 放在 __tests__文件夹下
测试结构
- describe: 将几个相关的测试放到一个组中,非必须
- test :(别名it)测试用例,是测试的最小单位
- expect:提供很多的matcher 来判定你的方法返回值是否符合特定条件
describe('add的方法测试',()=>{
test('2+3应该等于5',()=>{
expect(add(2,3)).toBe(5)
})
})
mock方法和处理
- Jest的mock方式 (Jest.fn()、Jest.spyOn()、Jest.mock())
- 预处理和后处理
- beforeAll / afterAll : 对测试文件中所有的用例开始前/ 后 进行统一的预处理
- beforeEach/ afterEach : 在每个用例开始前 / 后 进行预处理
覆盖率指标
在package.json中 设置 --coverage 即可 测试覆盖率
"test:unit": "vue-cli-service test:unit --coverage"
- %stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?
- %Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?
- %Funcs函数覆盖率(function coverage):是不是每个函数都调用了?
- %Lines行覆盖率(line coverage):是不是每一行都执行了?
配置好后,会生成一个coverage文件,然后打开里面的index.html 里面会有详细的信息展示
三种颜色分别代表不同比例的覆盖率(<50%红色,50%~80%灰色, ≥80%绿色)
旁边显示的1x代表执行的次数
好的测试覆盖率标准:80%以下不及格,90%以上可以使用,95%以上优秀
常用的方法
-
–mount: 创建一个包含被挂载和渲染的 Vue 组件的 wrapper,它仅仅挂载当前实例
-
—shallowMount:和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,只挂载一个组件而不渲染其子组件 (即保留它们的存根),这个方法可以保证你关心的组件在渲染时没有同时将其子组件渲染,避免了子组件可能带来的副作用(比如Http请求等)
-
—shallowMount和mount的区别:在文档中描述为"不同的是被存根的子组件",大白话就是shallowMount不会加载子组件,不会被子组件的行为属性影响该组件。
-
—单元测试的重点在"单元"二字,而不是"测试",想测试子组件再为子组件写对应的测试代码即可
-
—Wrapper:常见的有一下几种方法
-
—Wrapper:常见的有一下几种方法
- Wrapper:Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。
- Wrapper.vm:这是该 Vue 实例。你可以通过 wrapper.vm 访问一个实例所有的方法和属性。
- Wrapper.classes: 返回是否拥有该class的dom或者类名数组。
- Wrapper.find:返回第一个满足条件的dom。
- Wrapper.findAll:返回所有满足条件的dom。
- Wrapper.html:返回html字符串。
- Wrapper.text:返回内容字符串。
- Wrapper.setData:设置该组件的初始data数据。
- Wrapper.setProps:设置该组件的初始props数据。 (这是使用了,但没有效果)
- Wrapper.trigger:用来触发事件。
<template>
<div class="jest">
<div class="name">{{name}}</div>
<div class="name">{{name}}{{text}}</div>
<div class="text" @click="add">{{text}}</div>
</div>
</template>
<script src="./script.js">
export default {
name:"Foo",
props:{
name:{
type: String,
default: '啦啦啦'
}
},
data() {
return {
text: 123
}
},
methods:{
add(){
this.text += 1
}
}
}
</script>
开始测试
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'
describe('Foo', () => {
const wrapper = shallowMount(Foo)
console.log(Wrapper.classes()) //['jest']
console.log(Wrapper.classes('jest')) //true
console.log(Wrapper.find('.name').text()) // 切记如果是类的话,要加点 : 啦啦
console.log(Wrapper.findAll('.name')) //返回dom数组 WrapperArray { selector: '.name' }
console.log(Wrapper.findAll('.name').at(0)) //取dom数组中的第一个
Wrapper.setData({text : 3}) // 设置一个值
console.log(Wrapper.vm.text) // 3
Wrapper.setProps({name : "拉拉"})
console.log(Wrapper.vm.name) //这个结果仍 为 啦啦啦
Wrapper.find('.text').trigger("click")
console.log(Wrapper.vm.text) // 4
})
也可以初始化某些数据
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'
const wrapper = shallowMount(Foo,{
data() {
return {
bar: 'lala'
}
},
propsData:{
message: 'dd'
},
mocks: {
$route: {
query: {
aaa: '1',
}
},
$router: {
push: jest.fn(),
replace: jest.fn(),
}
}
})
Jest-Api(使用不同匹配器可以测试输入输出的值是否符合预期)toBe:判断是否相等
- toBeNull:判断是否为null
- toBeUndefined:判断是否为undefined
- toBeDefined:与上相反
- toBeNaN:判断是否为NaN
- toBeTruthy:判断是否为true
- toBeFalsy:判断是否为false
- toContain:数组用,检测是否包含
- toHaveLength:数组用,检测数组长度
- toEqual:对象用,检测是否相等
- toThrow:异常匹配
describe('Foo', () => {
expect(2 + 2).toBe(4)
expect(null).toBeNull()
expect(undefined).toBeUndefined()
let a = 1;
expect(a).toBeDefined()
a = 'ada';
expect(a).toBeNaN()
a = true;
expect(a).toBeTruthy()
a = false;
expect(a).toBeFalsy()
a = [1,2,3];
expect(a).toContain(2)
expect(a).toHaveLength(3)
a = {a:1};
expect(a).toEqual({a:1})
})