详解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 相关文章推荐
Jquery.LazyLoad.js修正版下载,实现图片延迟加载插件
Mar 12 Javascript
javascript的字符串按引用复制和传递,按值来比较介绍与应用
Dec 28 Javascript
js实现网页收藏功能
Dec 17 Javascript
使用JS轻松实现ionic调用键盘搜索功能(超实用)
Sep 06 Javascript
chrome下判断点击input上标签还是其余标签的实现方法
Sep 18 Javascript
jquery仿苹果的时间/日期选择效果
Mar 08 Javascript
jQuery实现注册会员时密码强度提示信息功能示例
Sep 05 jQuery
jQuery实现火车票买票城市选择切换功能
Sep 15 jQuery
jQuery中each和js中forEach的区别分析
Feb 27 jQuery
javascript触发模拟鼠标点击事件
Jun 26 Javascript
JavaScript变量基本使用方法实例分析
Nov 15 Javascript
Node Express用法详解【安装、使用、路由、中间件、模板引擎等】
May 13 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
PHP利用COM对象访问SQLServer、Access
2006/10/09 PHP
PHP 木马攻击防御技巧
2009/06/13 PHP
php 注释规范
2012/03/29 PHP
php 操作调试的方法
2012/07/12 PHP
解析isset与is_null的区别
2013/08/09 PHP
php防止SQL注入详解及防范
2013/11/12 PHP
Yii实现MySQL多数据库和读写分离实例分析
2014/12/03 PHP
Eclipse PHPEclipse 配置的具体步骤
2017/08/08 PHP
使用jQuery和PHP实现类似360功能开关效果
2014/02/12 Javascript
Javascript获取图片原始宽度和高度的方法详解
2016/09/20 Javascript
RequireJs的使用详解
2017/02/19 Javascript
Angularjs中使用指令绑定点击事件的方法
2017/03/30 Javascript
微信小程序progress组件使用详解
2018/01/31 Javascript
angularJs select绑定的model取不到值的解决方法
2018/10/08 Javascript
vue 中 命名视图的用法实例详解
2019/08/14 Javascript
node.js中fs文件系统模块的使用方法实例详解
2020/02/13 Javascript
[01:22]DOTA2神秘商店携大量周边降临完美大师赛
2017/11/07 DOTA
Python argv用法详解
2016/01/08 Python
使用C#配合ArcGIS Engine进行地理信息系统开发
2016/02/19 Python
python函数式编程学习之yield表达式形式详解
2018/03/25 Python
使用Python快速搭建HTTP服务和文件共享服务的实例讲解
2018/06/04 Python
python如果快速判断数字奇数偶数
2019/11/13 Python
Python imutils 填充图片周边为黑色的实现
2020/01/19 Python
细说CSS3中的选择符
2008/10/17 HTML / CSS
HTML5上传文件显示进度的实现代码
2012/08/30 HTML / CSS
SISLEY希思黎官方旗舰店:享誉全球的奢华植物美容品牌
2018/04/25 全球购物
支教自我鉴定
2014/01/18 职场文书
《特殊的葬礼》教学反思
2014/04/27 职场文书
党建工作先进材料
2014/05/02 职场文书
法人身份证明书
2014/10/08 职场文书
讲座通知范文
2015/04/23 职场文书
小学毕业感言100字
2015/07/30 职场文书
公安忠诚教育心得体会
2016/01/23 职场文书
导游词之蓬莱长岛
2019/12/17 职场文书
CentOS MySql8 远程连接实战
2022/04/19 MySQL
macos系统如何实现微信双开? mac登录两个微信以上微信的技巧
2022/07/23 数码科技