详解Vue单元测试Karma+Mocha学习笔记


Posted in Javascript onJanuary 31, 2018

在使用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 执行过程

  1. 执行 npm run unit 命令
  2. 开启Karma运行环境
  3. 使用Mocha去逐个测试用Chai断言写的测试用例
  4. 在终端显示测试结果
  5. 如果测试成功,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') 
 })
})

需要知道的知识点:

  1. 测试脚本都要放在 test/unit/specs/ 目录下。
  2. 脚本命名方式为  [组件名].spec.js。
  3. 所谓断言,就是对组件做一些操作,并预言产生的结果。如果测试结果与断言相同则测试通过。
  4. 单元测试默认测试 src 目录下除了 main.js 之外的所有文件,可在 test/unit/index.js 文件中修改。
  5. Chai断言库中,to be been is that which and has have with at of same 这些语言链是没有意义的,只是便于理解而已。
  6. 测试脚本由多个  descibe 组成,每个 describe 由多个 it 组成。
  7. 了解异步测试
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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
对象的类型:本地对象(1)
Dec 29 Javascript
Javascript 事件流和事件绑定
Jul 16 Javascript
通用javascript脚本函数库 方便开发
Oct 13 Javascript
jQuery对象的length属性用法实例
Dec 27 Javascript
js判断浏览器类型及设备(移动页面开发)
Jul 30 Javascript
js仿京东轮播效果 选项卡套选项卡使用
Jan 12 Javascript
jQuery.form.js的使用详解
Jun 14 jQuery
浅谈Vue 初始化性能优化
Aug 31 Javascript
webpack打包node.js后端项目的方法
Mar 10 Javascript
原生JS实现留言板
Mar 26 Javascript
基于canvas实现手写签名(vue)
May 21 Javascript
vue element ui validate 主动触发错误提示操作
Sep 21 Javascript
Vue单页面应用保证F5强刷不清空数据的解决方案
Jan 31 #Javascript
Vue服务器渲染Nuxt学习笔记
Jan 31 #Javascript
微信小程序页面生命周期详解
Jan 31 #Javascript
在vue项目中使用Nprogress.js进度条的方法
Jan 31 #Javascript
pace.js和NProgress.js两个加载进度插件的一点小总结
Jan 31 #Javascript
微信小程序App生命周期详解
Jan 31 #Javascript
jQuery NProgress.js加载进度插件的简单使用方法
Jan 31 #jQuery
You might like
为php4加入动态flash文件的生成的支持
2006/10/09 PHP
PHP4 与 MySQL 数据库操作函数详解
2006/10/09 PHP
如何在PHP中使用正则表达式进行查找替换
2013/06/13 PHP
php_screw安装使用教程(另一个PHP代码加密实现)
2014/05/29 PHP
yii的CURD操作实例详解
2014/12/04 PHP
php实现简单的MVC框架实例
2015/09/23 PHP
jQuery UI-Draggable 参数集合
2010/01/10 Javascript
Webkit的跨域安全问题说明
2011/09/13 Javascript
自己编写的类似JS的trim方法
2013/10/09 Javascript
ie中js创建checkbox默认选中问题探讨
2013/10/21 Javascript
jquery解析xml字符串简单示例
2014/04/11 Javascript
JavaScript设计模式之单例模式实例
2014/09/24 Javascript
JS与jQuery遍历Table所有单元格内容的方法
2015/12/07 Javascript
详解windows下vue-cli及webpack 构建网站(三)使用组件
2017/06/17 Javascript
vue2.0全局组件之pdf详解
2017/06/26 Javascript
Vuejs开发环境搭建及热更新【推荐】
2018/09/07 Javascript
详解超简单的react服务器渲染(ssr)入坑指南
2019/02/28 Javascript
使用js在layui中实现上传图片压缩
2019/06/18 Javascript
layer iframe 设置关闭按钮的方法
2019/09/12 Javascript
Jquery高级应用Deferred对象原理及使用实例
2020/05/28 jQuery
[03:22]DSPL第一期精彩集锦:酷炫到底!
2014/11/07 DOTA
Python version 2.7 required, which was not found in the registry
2014/08/26 Python
Python脚本实现网卡流量监控
2015/02/14 Python
python下解压缩zip文件并删除文件的实例
2018/04/24 Python
Django如何使用第三方服务发送电子邮件
2019/08/14 Python
matplotlib.pyplot画图并导出保存的实例
2019/12/07 Python
CSS3实现歌词进度文字颜色填充变化动态效果的思路详解
2020/06/02 HTML / CSS
css3 transform过渡抖动问题解决
2020/10/23 HTML / CSS
Pretty Little Thing爱尔兰:时尚女性服饰
2017/03/27 全球购物
三八妇女节演讲稿
2014/05/27 职场文书
2015年推广普通话演讲稿
2015/03/20 职场文书
2015年组织委员工作总结
2015/04/23 职场文书
领导干部学习三严三实心得体会
2016/01/05 职场文书
一篇文章学会Vue中间件管道
2021/06/20 Vue.js
MySQL系列之三 基础篇
2021/07/02 MySQL
中国古风插画师排行榜:夏达第一,第三是阴阳师姑获鸟皮肤创作者
2022/03/18 国漫