Vue单元测试Karma+Mocha
Vue单元测试Karma+Mocha
Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner)。该工具在Vue中的主要作用是将项目运行在各种主流Web浏览器进行测试。
换句话说,它是一个测试工具,能让你的代码在浏览器环境下测试。需要它的原因在于,你的代码可能是设计在浏览器端执行的,在node环境下测试可能有些bug暴露不出来;另外,浏览器有兼容问题,karma提供了手段让你的代码自动在多个浏览器(chrome,firefox,ie等)环境下运行。如果你的代码只会运行在node端,那么你不需要用karma。
Mocha是一个测试框架,在vue-cli中配合chai断言库实现单元测试。
Mocha的常用命令和用法不算太多,看阮一峰老师的测试框架 Mocha 实例教程就可以大致了解了。
而Chai断言库可以看Chai.js断言库API中文文档,很简单,多查多用就能很快掌握。
对测试框架的理解
npm run unit 执行过程
执行 npm run unit 命令
开启Karma运行环境
使用Mocha去逐个测试用Chai断言写的测试用例
在终端显示测试结果
如果测试成功,karma-coverage 会在 ./test/unit/coverage 文件夹中生成测试覆盖率结果的网页。
Karma的Vue的karma配置,简单注释了下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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 组成。
* 了解异步测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 }); |
实例:
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 对象教程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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 方法的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | 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组件的单元测试有很大帮助。
------------
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 回调中进行。
1 2 3 4 5 6 7 8 | 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(); }); }); |
golang技术交流群:316397059,vuejs技术交流群:458915921 囤币一族:621258209,有兴趣的可以加入
微信公众号: 心禅道(xinchandao)投资论道
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
2016-01-28 如何安装nginx_lua_module模块,升级nginx,nginx-lua-fastdfs-GraphicsMagick动态生成缩略图,实现图片自动裁剪缩放
2012-01-28 谈谈android反编译和防止反编译的方法