详解Vue单元测试Karma+Mocha学习笔记
在使用vue-cli创建项目的时候,会提示要不要安装单元测试和e2e测试。既然官方推荐我们使用这两个测试框架,那么我们就动手去学习实践一下他们吧。
简介
karma
karma是一个基于node.js的javascript测试执行过程管理工具(test runner)。该工具在vue中的主要作用是将项目运行在各种主流web浏览器进行测试。
换句话说,它是一个测试工具,能让你的代码在浏览器环境下测试。需要它的原因在于,你的代码可能是设计在浏览器端执行的,在node环境下测试可能有些bug暴露不出来;另外,浏览器有兼容问题,karma提供了手段让你的代码自动在多个浏览器(chrome,firefox,ie等)环境下运行。如果你的代码只会运行在node端,那么你不需要用karma。
mocha
mocha是一个测试框架,在vue-cli中配合chai断言库实现单元测试。
而chai断言库可以看chai.js断言库api中文文档,很简单,多查多用就能很快掌握。
我对测试框架的理解
npm run unit 执行过程
- 执行 npm run unit 命令
- 开启karma运行环境
- 使用mocha去逐个测试用chai断言写的测试用例
- 在终端显示测试结果
- 如果测试成功,karma-coverage 会在 ./test/unit/coverage 文件夹中生成测试覆盖率结果的网页。
karma
对于karma,我只是了解了一下它的。
下面是vue的karma配置,简单注释了下:
var webpackconfig = require('../../build/webpack.test.conf') module.exports = function (config) { config.set({ // 浏览器 browsers: ['phantomjs'], // 测试框架 frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], // 测试报告 reporters: ['spec', 'coverage'], // 测试入口文件 files: ['./index.js'], // 预处理器 karma-webpack preprocessors: { './index.js': ['webpack', 'sourcemap'] }, // webpack配置 webpack: webpackconfig, // webpack中间件 webpackmiddleware: { noinfo: true }, // 测试覆盖率报告 // https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md coveragereporter: { dir: './coverage', reporters: [ { type: 'lcov', subdir: '.' }, { type: 'text-summary' } ] } }) }
mocha和chai
我们看下官方的例子(都用注释来解释代码意思了):
import vue from 'vue' // 导入vue用于生成vue实例 import hello from '@/components/hello' // 导入组件 // 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite) describe('hello.vue', () => { // 每个describe块应该包括一个或多个it块,称为测试用例(test case) it('should render correct contents', () => { const constructor = vue.extend(hello) // 获得hello组件实例 const vm = new constructor().$mount() // 将组件挂在到dom上 //断言:dom中class为hello的元素中的h1元素的文本内容为welcome to your vue.js app expect(vm.$el.queryselector('.hello h1').textcontent) .to.equal('welcome to your vue.js app') }) })
需要知道的知识点:
- 测试脚本都要放在 test/unit/specs/ 目录下。
- 脚本命名方式为 [组件名].spec.js。
- 所谓断言,就是对组件做一些操作,并预言产生的结果。如果测试结果与断言相同则测试通过。
- 单元测试默认测试 src 目录下除了 main.js 之外的所有文件,可在 test/unit/index.js 文件中修改。
- chai断言库中,to be been is that which and has have with at of same 这些语言链是没有意义的,只是便于理解而已。
- 测试脚本由多个 descibe 组成,每个 describe 由多个 it 组成。
- 了解异步测试
it('异步请求应该返回一个对象', done => { request .get('https://api.github.com') .end(function(err, res){ expect(res).to.be.an('object'); done(); }); });
了解一下 describe 的钩子(生命周期)
describe('hooks', function() { before(function() { // 在本区块的所有测试用例之前执行 }); after(function() { // 在本区块的所有测试用例之后执行 }); beforeeach(function() { // 在本区块的每个测试用例之前执行 }); aftereach(function() { // 在本区块的每个测试用例之后执行 }); // test cases });
实践
上面简单介绍了单元测试的用法,下面来动手在vue中进行单元测试!
util.js
从vue官方的demo可以看出,对于vue的单元测试我们需要将组件实例化为一个vue实例,有时还需要挂载到dom上。
const constructor = vue.extend(hello) // 获得hello组件实例 const vm = new constructor().$mount() // 将组件挂载到dom上
以上写法只是简单的获取组件,有时候我们需要传递props属性、自定义方法等,还有可能我们需要用到第三方ui框架。所以以上写法非常麻烦。
这里推荐element的单元测试工具脚本util.js,它封装了vue单元测试中常用的方法。下面demo也是根据该 util.js来写的。
这里简单注释了下各方法的用途。
/** * 回收 vm,一般在每个测试脚本测试完成后执行回收vm。 * @param {object} vm */ exports.destroyvm = function (vm) {} /** * 创建一个 vue 的实例对象 * @param {object|string} compo - 组件配置,可直接传 template * @param {boolean=false} mounted - 是否添加到 dom 上 * @return {object} vm */ exports.createvue = function (compo, mounted = false) {} /** * 创建一个测试组件实例 * @param {object} compo - 组件对象 * @param {object} propsdata - props 数据 * @param {boolean=false} mounted - 是否添加到 dom 上 * @return {object} vm */ exports.createtest = function (compo, propsdata = {}, mounted = false) {} /** * 触发一个事件 * 注: 一般在触发事件后使用 vm.$nexttick 方法确定事件触发完成。 * mouseenter, mouseleave, mouseover, keyup, change, click 等 * @param {element} elm - 元素 * @param {string} name - 事件名称 * @param {*} opts - 配置项 */ exports.triggerevent = function (elm, name, ...opts) {} /** * 触发 “mouseup” 和 “mousedown” 事件,既触发点击事件。 * @param {element} elm - 元素 * @param {*} opts - 配置选项 */ exports.triggerclick = function (elm, ...opts) {}
示例一
示例一中我们测试了 hello 组件的各种元素的数据,学习 util.js 的 destroyvm 和 createtest 方法的用法以及如何获取目标元素进行测试。获取dom元素的方式可查看dom 对象教程。
hello.vue
<template> <div class="hello"> <h1 class="hello-title">{{ msg }}</h1> <h2 class="hello-content">{{ content }}</h2> </div> </template> <script> export default { name: 'hello', props: { content: string }, data () { return { msg: 'welcome!' } } } </script>
hello.spec.js
import { destroyvm, createtest } from '../util' import hello from '@/components/hello' describe('hello.vue', () => { let vm aftereach(() => { destroyvm(vm) }) it('测试获取元素内容', () => { vm = createtest(hello, { content: 'hello world' }, true) expect(vm.$el.queryselector('.hello h1').textcontent).to.equal('welcome!') expect(vm.$el.queryselector('.hello h2').textcontent).to.have.be.equal('hello world') }) it('测试获取vue对象中数据', () => { vm = createtest(hello, { content: 'hello world' }, true) expect(vm.msg).to.equal('welcome!') // chai的语言链是无意义的,可以随便写。如下: expect(vm.content).which.have.to.be.that.equal('hello world') }) it('测试获取dom中是否存在某个class', () => { vm = createtest(hello, { content: 'hello world' }, true) expect(vm.$el.classlist.contains('hello')).to.be.true const title = vm.$el.queryselector('.hello h1') expect(title.classlist.contains('hello-title')).to.be.true const content = vm.$el.queryselector('.hello-content') expect(content.classlist.contains('hello-content')).to.be.true }) })
输出结果
hello.vue
√ 测试获取元素内容
√ 测试获取vue对象中数据
√ 测试获取dom中是否存在某个class
示例二
示例二中我们使用 createtest 创建测试组件测试点击事件,用 createvue 创建vue示例对象测试组件 click 的使用。这里主要可以看下到 createvue 方法的使用。
click.vue
<template> <div> <span class="init-num">初始值为{{ initnum }}</span><br> <span class="click-num">点击了{{ clicknum }}次</span><br> <span class="result-num">最终结果为{{ resultnum }}</span><br> <button @click="add">累加{{ addnum }}</button> </div> </template> <script> export default { name: 'click', props: { addnum: { type: number, default: 1 }, initnum: { type: number, default: 1 } }, data () { return { clicknum: 0, resultnum: 0 } }, mounted () { this.resultnum = this.initnum }, methods: { add () { this.resultnum += this.addnum this.clicknum++ this.$emit('result', { clicknum: this.clicknum, resultnum: this.resultnum }) } } } </script>
click.spec.js
import { destroyvm, createtest, createvue } from '../util' import click from '@/components/click' describe('click.vue', () => { let vm aftereach(() => { destroyvm(vm) }) it('测试按钮点击事件', () => { vm = createtest(click, { addnum: 10, initnum: 11 }, true) let buttonelm = vm.$el.queryselector('button') buttonelm.click() buttonelm.click() buttonelm.click() // settimeout 的原因 // 在数据改变之后,界面的变化会有一定延时。不用timeout有时候会发现界面没有变化 settimeout(done => { expect(vm.resultnum).to.equal(41) expect(vm.$el.queryselector('.init-num').textcontent).to.equal('初始值为11') expect(vm.$el.queryselector('.click-num').textcontent).to.equal('点击了3次') expect(vm.$el.queryselector('.result-num').textcontent).to.equal('最终结果为41') done() }, 100) }) it('测试创建vue对象', () => { let result vm = createvue({ template: ` <click @click="handleclick"></click> `, props: { addnum: 10, initnum: 11 }, methods: { handleclick (obj) { result = obj } }, components: { click } }, true) vm.$el.click() vm.$nexttick(done => { expect(result).to.be.exist expect(result.clicknum).to.equal(1) expect(result.resultnum).to.be.equal(21) done() }) })
输出结果
click.vue
√ 测试按钮点击事件
√ 测试创建vue对象
其他
所有示例代码都放github仓库中便于查看。如果想查看更多好的测试用例,建议配合 util.js 看一下 element 的单元测试脚本的写法,里面有很多测试脚本可以供我们学习。作为被广大vue用户使用的ui组件库,测试脚本肯定也写很很不错的~甚至可以将这些脚本照抄一遍,相信这会对学习vue组件的单元测试有很大帮助。
下面是本人看element单元测试的笔记,供参考。
util.js 方法包含了大多数vue组件化的测试需求。
vm.$el vm.$nexttick 和 vm.$ref 都是在测试过程中比较常用的一些vue语法糖。
需要注意: vm.$nexttick 方法是异步的,所以需要在里面使用done方法。
异步断言,方法参数需要是 _ 或者 done
大多数时候查询元素通过 queryselector 方法查询class获得
vm.$el.queryselector('.el-breadcrumb').innertext
大多数情况下查询是否存在某个class通过 classlist.contains 方法获得,查找的结果为 true 或 false
vm.$el .classlist.contains('el-button--primary')
异步测试必须以 done() 方法结尾。settimeout 和 vm.$nexttick 是常用的异步测试。
实现按钮点击:通过获取按钮元素 btn,执行 btn.click() 方法实现。
由于 vue 进行异步更新dom 的情况,一些依赖dom更新结果的断言必须在 vue.nexttick 回调中进行。
triggerevent(vm.$refs.cascader.$el, 'mouseenter'); vm.$nexttick(_ => { vm.$refs.cascader.$el.queryselector('.el-cascader__clearicon').click(); vm.$nexttick(_ => { expect(vm.selectedoptions.length).to.be.equal(0); done(); }); });
vue.js学习系列项目地址:https://github.com/violetjack/vuestudydemos
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Vue服务器渲染Nuxt学习笔记
下一篇: 小程序九九八十一坑之头像模糊,谁的锅?