使用 karma、Mocha、chai 对vue项目进行单元测试。
vue-cli
脚手架工具创建新项目的时候,我们可以选择 创建包含单元测试 karma && Mocha
的项目。
git clone 项目下来,然后查看 test 目录里面的测试代码
git clone https://github.com/mind029/vue-unit-test.git
cd vue-unit-test
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
# build for production and view the bundle analyzer report
npm run build --report
# run unit tests
npm run unit
# run all tests
npm test
- vue组件加载后,data数据模型是否符合预期要求。
- 定义在 methods 方法是否可用
- filter是否可用
- 带有props的组件,数据能否正常传递
- 异步更新DOM的情况渲染的结果是否符合预期、比如 class、文字、属性等。
- 组件绑定事件是触发结构是否符合预期
- 子组件
data.spec.js
我们对Demo.vue 组件进行 data 默认数据测试
/* eslint-env node, mocha */
import Demo from '@/components/Demo'
/* global expect */
describe('# Demo组件 data 初始化数据测试', () => {
it('# 组件名称name', () => {
expect(Demo.name).to.equal('demo')
})
it('# data is a function ', () => {
expect(Demo.data).to.be.an('function')
})
it('# data 默认数据检查', () => {
const defaultData = Demo.data()
expect(defaultData.msg).to.equal('this is base data')
expect(defaultData.count).to.equal(0)
})
})
hook.spec.js
对vue生命周期中的各个hook方法测试。
/* eslint-env node, mocha */
import Demo from '@/components/Demo'
/* global expect */
describe('# Demo组件hook方法测试', () => {
it('# 组件是否拥有 created 方法', () => {
expect(Demo.created).to.be.an('function')
})
it('# 组件是否拥有 beforeMount 方法', () => {
expect(Demo.beforeMount).to.be.an('function')
})
it('# 组件是否拥有 mounted 方法', () => {
expect(Demo.mounted).to.be.an('function')
})
it('# 组件是否拥有 beforeUpdate 方法', () => {
expect(Demo.beforeUpdate).to.be.an('function')
})
it('# 组件是否拥有 updated 方法', () => {
expect(Demo.updated).to.be.an('function')
})
it('# 组件是否拥有 beforeDestroy 方法', () => {
expect(Demo.beforeDestroy).to.be.an('function')
})
it('# 组件是否拥有 destroyed 方法', () => {
expect(Demo.destroyed).to.be.an('function')
})
})
methods.spec.js
对组件methods里面的方法进行测试
/* eslint-env node, mocha */
import { createTest, destroyVM } from '../../util'
import Demo from '@/components/Demo'
/* global expect */
describe('# Demo组件 methods 测试', () => {
it('# 是否有用 addCount 函数 ', () => {
expect(Demo.methods.addCount).to.be.an('function')
})
it('# 是否有用 getData 函数 ', () => {
expect(Demo.methods.getData).to.be.an('function')
})
it('# 测试 getData ,返回数据是否符合预期 [1, 2, 3]', () => {
expect(Demo.methods.getData()).to.eql([1, 2, 3])
})
// 测试 addCount �方法是否 正常,这个没检查 render。所以可以不用 setTimeout done
// destroyVM 的目的是不每次都销毁实例,不让改变的数据影响后面的测试
it('# 测试 addCount �方法是否正常', () => {
const vm = createTest(Demo, {}, true)
let oldCount = vm.count
vm.addCount()
let newCount = vm.count
expect(newCount).to.equal(oldCount + 1)
destroyVM(vm)
})
it('# 测试 addCount �改变后 render 是否正常', (done) => {
const vm = createTest(Demo, {}, true)
let oldCount = vm.count
// 触发方法,让count++
vm.addCount()
// 因为页面渲染不是同步,是promise,比setTimeout块,所以需要异步等待等渲染完成
setTimeout(() => {
// 获得页面渲染值
let renderCount = Number(vm.$el.querySelector('.count').textContent)
expect(renderCount).to.equal(oldCount + 1)
done()
}, 20)
})
it('# 测试 页面 click 触发 addCount 是否 正常', (done) => {
const vm = createTest(Demo, {}, true)
let oldCount = vm.count
vm.$el.querySelector('#changeCount').click()
// 判断 count 是否加1
expect(vm.count).to.equal(oldCount + 1)
// 因为页面渲染不是同步,是promise,比setTimeout块,所以需要异步等待等渲染完成
setTimeout(() => {
// 获得页面渲染值
let renderCount = Number(vm.$el.querySelector('.count').textContent)
expect(renderCount).to.equal(vm.count)
destroyVM(vm)
done()
}, 20)
})
})
filter.spec.js
对 filters里面的过滤器进行测试
/* eslint-env node, mocha */
import { createTest, destroyVM } from '../../util'
import Demo from '@/components/Demo'
/* global expect */
describe('# Demo组件 filters 测试', () => {
it('# filters 是否有用 upperCase 函数 ', () => {
expect(Demo.filters.upperCase).to.be.an('function')
})
it('# 测试 filters upperCase ,返回数据是否符合预期 字母变大写', () => {
expect(Demo.filters.upperCase('a')).to.equal('A')
})
it('# 测试 页面 filters upperCase 是否 正常', (done) => {
const vm = createTest(Demo, {}, true)
// 因为页面渲染不是同步,是promise,比setTimeout块,所以需要异步等待等渲染完成
setTimeout(() => {
// 获得页面渲染值
let upperCaseStr = vm.$el.querySelector('.upperCase').textContent
expect(upperCaseStr).to.equal(vm.upper.toUpperCase())
destroyVM(vm)
done()
}, 20)
})
})
我们想要测试的 vue组件/模块 中的 代码尽可能按照 单一职责原则,这种模式来开发,这样才方便测试,举例
详细实现可以查看 vue-unit-test/src/pages/Home.vue
伪代码:
import dataApi from '@/api/data'
export default {
name: 'api',
created () {
this.initData()
},
data () {
return {
webInfo: {}
}
},
methods: {
// 初始化数据
async initData () {
let webInfoData = await this.getWebInfo()
this.setWebInfo(webInfoData)
},
// 获取数据 写 在一个方法上
async getWebInfo () {
let result = await dataApi.webInfo()
return result.data
},
// 设置数据到 data 上。
async setWebInfo (webInfo) {
// 对 webInfo 进行相关数据校验
// 设置数据到data上,然后vue自动渲染更新html
this.webInfo = webInfo
}
}
}
- 把获取后端的数据的功能提取到
getWebInfo()
上 - 把设置data,然后render 提取到
setWebInfo(webInfo)
上
这样我们在编写测试用例的时候即可利用我们的模拟数据,调用 setWebInfo(data)
,来检查我们 vue组件是否渲染正确。
多看别人怎么写测试用例,是最快的学习方法。
推荐看 elementUI 框架库里面的各个组件测试代码,看了就懂要测试什么了。
- 测试框架 Mocha 实例教程
- Mocha测试框架
- karma测试框架
- chai
- 前端单元测试探索
- vue单元测试
- Vue Test Utils
- 《JavaScript设计模式与开发实践》