安装vue-cli
vue-test-utils 是 Vue 官方的测试库,并将在本指南中贯穿始终。它在浏览器和Node.js环境中皆可运行,并能配合任何 test runner 使用。在本指南中,我们将在 Node.js 环境运行测试。
vue-cli 是起步的最简单方式。它将建立一个项目,也会配置 Jest,一个流行的测试框架。其安装方法是:
yarn global add @vue/cli
或通过 npm:
npm install -g @vue/cli
通过运行 vue create [project-name] 来创建一个新项目。选择 "Manually select features" 和 "Unit Testing",以及 "Jest" 作为 test runner。
一旦安装完成,cd 进入项目目录中并运行 yarn test:unit。如果一切顺利,你将看到:
PASS tests/unit/HelloWorld.spec.js
HelloWorld.vue
✓ renders props.msg when passed (26ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.074s
恭喜,你已经运行了你的第一个通过的测试!
安装@vue/cli-plugin-unit-jest
vue add @vue/cli-plugin-unit-jest
如果你的本地依赖的vue和你全局的vue版本不一样你需要升级。
安装完成后你会发现本地多了一些文件
jest.config.js
jest的配置文件就是这个,他会有默认配置。具体要改什么,需要的时候查一下,如果基础版可以不管。
逐一注释讲解配置的作用:
const path = require('path')
module.exports = {
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
// rootDir:其实就是指整个项目的根目录,也就是最外层的目录。这里多句嘴,再解释下path.resolve(__dirname,"../../")的意义,他最终返回的结果是该问见所在的根目录,简单来说__dirname返回的是当前目录,再向上两层,就是整个项目的根目录了。
rootDir: path.resolve(__dirname, './'),
testMatch: ['**/test/unit/**/*.(spec|test).[jt]s?(x)', '**/__test__/**/*.(spec|test).[jt]s?(x)'],
// moduleNameMapper:一种正则表达式到模块名的映射,匹配到的文件的内容可以是空的。我理解的是,可以通过该参数,来mock一些图片,css等静态资源文件,因为我们在测试的时候实际上是不太需要这些文件的,但是有需要引入它作为环境上的依赖。
moduleNameMapper: {
'^@esign-ui/base/(.*)$': '<rootDir>/packages/base/$1',
'@esign-ui/biz/(.*)$': '<rootDir>/packages/biz/$1',
'@esign-ui/shared/(.*)$': '<rootDir>/packages/shared/$1',
'@esign-ui/helpers/(.*)$': '<rootDir>/packages/helpers/$1',
},
// transform:简单来说就是转换器,正则匹配到的文件可以通过对应模块的转换器来解决一些未来版本语法时可以使用它。通过正则来匹配文件,为匹配到的文件使用对应的模块。
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/test/unit/fileTransformer.js',
},
// snapshotSerializers:快照测试的插件,会生成测试文件的一个快照版本,可以再package.json中查看安装的快照插件。
snapshotSerializers: [<rootDir>/node_modules/jest-serializer-vue],
// setupFiles:运行一些测试环境所要依赖的模块的路径列表,比如引入vue,elementUI等插件的列表,以给测试提供完整的环境。
setupFiles: ['jest-canvas-mock', '<rootDir>/test/unit/setup'],
// collectCoverage:是否收集测试时的覆盖率信息。
collectCoverage: true,
// coverageDirectory:jest输出覆盖率信息文件的目录。
coverageDirectory: '<rootDir>/test/unit/coverage',
// collectCoverageFrom:为数组中匹配的文件收集覆盖率信息,即使并没有为该文件写相关的测试代码,需要将collectCoverage设置为true,或者通过--corverage参数来调用jest。
collectCoverageFrom: [
// 覆盖目录
'<rootDir>/src/**/*/*.{js,vue,jsx,tsx,ts}',
// 不覆盖目录
'!**/node_modules/**',
],
}
此时package.json多出来了两个脚本命令,一个是lint检查,一个就是我们将要使用的单元测试脚本了。
yarn test:unit
或者
npm run test:unit
增加test文件夹
根目录增加了test文件夹,test下面又有unit文件夹,下面就是单元测试demo文件example.spec.ts。具体的单元测试用例就写在这里。
// 简单的用例
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
})
单测的测试范围
通过 Jest 运行单元测试。默认的 testMatch 是 <rootDir>/(test/unit/**/*.(spec|test).(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)) 。
匹配:任何
test/unit 中以 .spec.(js|jsx|ts|tsx)结尾的文件;任何__tests__目录中的js(x)/ts(x)文件。 使用:vue-cli-service test:unit [options] <regexForTestFiles>支持所有的 Jest 命令行选项。
安装过程遇到的问题
1.未安装babel-jest
修复方法: npm i -D babel-jest or yarn add babel-jest -D
2.版本不支持
修复方法: 将babel-jest版本调整到24.0.0-25.0.0之间就好了。
3.需要注入store/vuex/vue-router等一类的报错
修复方法:需要将store/vuex/vue-router等用到的插件mock掉。
4.element-ui 组建的事件无法解析
修复方法:input事件是input组建emit出来的,需要自己模拟hack掉,不能直接触发。
单测覆盖率指标
%stmts是语句覆盖率(statement coverage):是不是每个语句都执行了? %Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了? %Funcs函数覆盖率(function coverage):是不是每个函数都调用了? %Lines行覆盖率(line coverage):是不是每一行都执行了?
常用简单的jest语法和使用方式
- describe
用来描述一个测试用例的作用和细节
describe('填写页面的测试用例', () => {
// ... 测试用例
})
- it
jest的测试基础单元
it('unit test describe',() => {
//...测试的主体内容
})
- expect
Jest为我们提供了expect函数用来包装被测试的方法并返回一个对象,该对象中包含一系列的匹配器来让我们更方便的进行断言。
test('2+2=4', () => {
expect(2 + 2).toBe(4);
});
常用的几个断言(匹配器)
- .not
test('the best flavor is not coconut', () => {
expect(bestLaCroixFlavor()).not.toBe('coconut');
});
- .toBe()
const can = {
name: 'pamplemousse',
ounces: 12,
};
describe('the can', () => {
test('has 12 ounces', () => {
expect(can.ounces).toBe(12);
});
test('has a sophisticated name', () => {
expect(can.name).toBe('pamplemousse');
});
});
- .toEqual()
const can1 = {
flavor: 'grapefruit',
ounces: 12,
};
const can2 = {
flavor: 'grapefruit',
ounces: 12,
};
describe('the La Croix cans on my desk', () => {
test('have all the same properties', () => {
expect(can1).toEqual(can2);
});
test('are not the exact same can', () => {
expect(can1).not.toBe(can2);
});
});
- .toHaveLength()
expect([1, 2, 3]).toHaveLength(3);
expect('abc').toHaveLength(3);
expect('').not.toHaveLength(5);
- .toThrow()
test('throws on octopus', () => {
expect(() => {
drinkFlavor('octopus');
}).toThrow();
});
- .toMatch()
describe('an essay on the best flavor', () => {
test('mentions grapefruit', () => {
expect(essayOnTheBestFlavor()).toMatch(/grapefruit/);
expect(essayOnTheBestFlavor()).toMatch(new RegExp('grapefruit'));
});
});
describe('grapefruits are healthy', () => {
test('grapefruits are a fruit', () => {
expect('grapefruits').toMatch('fruit');
});
});
- expect.assertions(number) 期待断言的次数
shallowMount和mount的区别(重点)
shallowMount和mount的结果是个被封装的Wrapper,可以进行多种操作,譬如find()、parents()、children()等选择器进行元素查找;state()、props()进行数据查找,setState()、setprops()操作数据;simulate()模拟事件触发。shallowMount只渲染当前组件,只能能对当前组件做断言;mount会渲染当前组件以及所有子组件,对所有子组件也可以做上述操作。一般交互测试都会关心到子组件,我使用的都是mount。但是mount耗时更长,内存啥的也都占用的更多,如果没必要操作和断言子组件,可以使用shallowMount。
mock
类似这样的公共函数,我们在写组件的单测时组件内部会用到,所以我们就要将这些函数给mock掉,避免公共函数的加载或者依赖带来的报错或者解析问题。
jest.mock('@/lib/http/will', ()=>{
rerurn {
pwdAuth(xxx) {
return new Promise(resolve, reject) {
if(xxx) {
resolve(xxx)
} else {
reject()
}
}
}
}
})
希望对大家有所帮助。 送上我经常看的jest的文档地址 jest的文档:jestjs.io/docs/zh-Han… Vue测试指南: lmiller1990.github.io/vue-testing…