使用 vue-cli 可以直接生成一个包含 unit & e2e 测试的开发环境。这里我们主要针对 unit 文件进行 单元测试 。 (本发首发于我的笔记网站)
命令行效果预览
test/unit 文件结构及分析
├── coverage
├── jest.conf.js
├── setup.js
└── specs
├── api-test.spec.js
├── click-test.spec.js
├── data-test.spec.js
├── dom-test.spec.js
├── input-test.spec.js
├── mock-test.spec.js
└── props-test.spec.js
- coverage: 单元测试覆盖率的报表生成文件(当执行
npm run unit后,可在该文件夹下打开index.html文件查看覆盖率)。 - jest.conf.js: jest的配置文件。Jest是Facebook开发的一个对javascript进行单元测试的工具,之前仅在其内部使用,后开源,并且是在Jasmine测试框架上演变开发而来,使用了我们熟知的expect(value).toBe(other)这种断言格式。
- setup.js: 默认生成的vue配置文件(不用更改,默认开发环境)。
- specs: 编写单元测试的文件夹(所有的测试文件都将放在该文件夹下)。
data方法测试
假设我们编写了这样一个组件:
<!-- data-test.vue -->
<template>
<span>{{ msg }}</span>
</template>
<script>
export default {
name: 'TestData',
data() {
return {
msg: 'hello'
}
},
created() {
this.msg = 'bye'
}
}
</script>
那么,我们可以这样编写断言
// data.spec.js
// 导入vue.js和组件,进行测试
import Vue from 'vue'
import TestDataComponent from '@/components/data-test.vue'
describe('TestDataComponent', () => {
// 检查原始组件选项
it('has a created hook', () => {
expect(typeof TestDataComponent.created).toEqual('function')
})
// 评估原始组件选项中的函数的结果
it('should set the correct default data', () => {
expect(typeof TestDataComponent.data).toEqual('function')
const data = TestDataComponent.data()
expect(data.msg).toEqual('hello')
})
// 检查mount中的组件实例
it('should correctly set the msg when created', () => {
const vm = new Vue(TestDataComponent).$mount()
expect(vm.msg).toEqual('bye')
})
// 创建一个实例并检查渲染输出
it('should render correct msg', () => {
const Ctor = Vue.extend(TestDataComponent)
const vm = new Ctor().$mount()
expect(vm.$el.textContent).toEqual('bye')
})
})
props测试
很多组件的渲染输出由它的 props 决定。事实上,如果一个组件的渲染输出完全取决于它的 props,那么它会让测试变得简单,就好像断言不同参数的纯函数的返回值。例子:
<template>
<!-- props-test.vue -->
<p>{{ msg }}</p>
</template>
<script>
export default {
props: ['msg']
}
</script>
那么我们就可以在不同的 props 中,通过 propsData 选项断言它的渲染输出:
// props.spec.js
import Vue from 'vue'
import TestPropsComponent from '@/components/props-test.vue'
const Ctor = Vue.extend(TestPropsComponent);
// 针对props,可以通过propsData选项断言它的渲染输出
const getRenderText = (propsData) => {
const vm = new Ctor({propsData}).$mount()
return vm.$el.textContent
}
describe('TestPropsComponent', () => {
it('should render correctly with different props', () => {
expect(getRenderText({msg: 'Hello'})).toEqual('Hello')
expect(getRenderText({msg: 'Bye'})).toEqual('Bye')
})
})
断言异步更新
由于 Vue 进行 异步更新 DOM 的情况,一些依赖 DOM 更新结果的断言必须在 Vue.nextTick 回调中进行:
<template>
<!-- dom-test.vue -->
<span>{{ msg }}</span>
</template>
<script>
export default {
data() {
return {
msg: 'Hello'
}
}
}
</script>
断言的编写:
// dom.spec.js
import Vue from 'vue'
import DomUpdateComponent from '@/components/dom-test.vue'
describe('DomUpdateComponent', () => {
it('the msg should be Hello', () => {
const data = DomUpdateComponent.data()
expect(data.msg).toEqual('Hello')
})
// 在状态更新后检查生成的HTML
it('the msg should change to Bye', () => {
const vm = new Vue(DomUpdateComponent).$mount()
vm.msg = 'Bye'
// 在状态改变后和断言 DOM 更新前等待一刻
Vue.nextTick(() => {
expect(vm.$el.textContent).toEqual('Bye')
})
})
})
组件事件的测试
假设我们编写了这样一个组件,其中有一个按钮,按钮有一个 点击事件 :
<!-- click-test.vue -->
<template>
<div>
<p>现在是{{ num }}</p>
<button @click="increase">change</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
methods: {
increase() {
this.num++
}
}
}
</script>
那么,我们可以这样在断言中触发事件:
// click.spec.js
import Vue from 'vue'
import ClickTestComponent from '@/components/click-test.vue'
describe('ClickTestComponent', () => {
const Ctor = Vue.extend(ClickTestComponent)
const vm = new Ctor().$mount()
// 获取按钮
const oBtn = vm.$el.querySelector('button')
// 创建点击事件
const clickEvent = new window.Event('click')
it("the num should be 1", () => {
// 触发按钮点击事件
oBtn.dispatchEvent(clickEvent)
// 需要手动监听更新
vm._watcher.run()
expect(vm.$el.textContent).toContain('1')
})
it("the num should be 2", () => {
// 触发按钮点击事件
oBtn.dispatchEvent(clickEvent)
// 需要手动监听更新
vm._watcher.run()
expect(vm.$el.textContent).toContain('2')
})
})
用户交互组件的测试
假设我们有一个输入框,并且绑定了一些事件:
<template>
<!-- input-test.vue -->
<div>
<p>{{ msg }}</p>
<input type="text" v-model="input">
<button @click="handle"></button>
</div>
</template>
<script>
export default {
data() {
return {
msg: '',
input: 'hi'
}
},
methods: {
handle() {
this.msg = this.input
}
}
}
</script>
很显然,我们是要利用 v-model 模拟 input 进行断言:
// input.spec.js
import Vue from 'vue'
import InputTestComponent from '@/components/input-test.vue'
describe('InputTestComponent', () => {
const Ctor = Vue.extend(InputTestComponent)
const vm = new Ctor().$mount()
const oBtn = vm.$el.querySelector('button')
const clickEvent = new window.Event('click')
it('the msg should be equal with input', () => {
vm.input = 'let me change msg to what I say'
oBtn.dispatchEvent(clickEvent)
// 手动监听更新
vm._watcher.run()
expect(vm.$el.textContent).toContain('let me change msg to what I say')
})
});
API的测试
- 利用
mock.js模拟后端接口 在src文件夹下新建mock文件夹,编写index.js:
// mock/index.js
import Mock from 'mockjs'
export default Mock.mock('http://api.rs.com/v1/test', {
'greeting': 'Hi, Vue'
});
- 利用
axios进行ajax 在src文件夹下新建provider文件夹,编写index.js:
// provider/index.js
import axios from 'axios'
import '../mock/index'
const getGreeting = async (url = 'http://api.rs.com/v1/test') => {
try {
const res = await axios.get(url)
return res.data
} catch (err) {
throw new Error('获取问候语失败')
}
}
export default getGreeting
- 编写组件
<template>
<!-- mock-test.vue -->
<p>{{ msg }}</p>
</template>
<script>
import getGreeting from '@/provider/index'
export default {
data() {
return {
msg: 'waiting'
}
},
created() {
this.getMsg()
},
methods: {
async getMsg(url) {
try {
const data = await getGreeting(url)
this.msg = data.greeting
} catch (err) {
console.log(err.message)
}
}
}
}
</script>
- 针对
provider/index.js进行单元测试
// api.spec.js
import getGreeting from '@/provider/index'
describe('test greeting', async () => {
it('res should be "Hi, Vue"', async () => {
const res = await getGreeting();
expect(res.greeting).toEqual('Hi, Vue')
})
it('err should be catched', async () => {
try {
const res = await getGreeting('wrongUrl')
} catch (err) {
expect(err.message).toEqual('获取问候语失败')
}
})
})
- 针对编写的组件进行测试
// mock-test.spec.js
import Vue from 'vue'
import MockTestComponet from '@/components/mock-test.vue'
describe('MockTestComponet', () => {
it('should have the created hook', () => {
expect(typeof MockTestComponet.created).toEqual('function')
})
it('the msg should be waiting', () => {
const data = MockTestComponet.data()
expect(data.msg).toEqual('waiting')
})
it('the msg should change when created', async () => {
// const vm = new Vue(MockTestComponet).$mount()
const Ctor = Vue.extend(MockTestComponet)
const vm = new Ctor().$mount()
setTimeout(() => {
expect(vm.msg).toEqual('Hi, Vue')
}, 0)
})
// 返回正确的结果
it('test methods getMsg by correct url', async () => {
const vm = new Vue(MockTestComponet)
await vm.getMsg();
expect(vm.msg).toEqual('Hi, Vue')
})
// 抛出异常
it('test methods getMsg by wrong url', async () => {
try {
const vm = new Vue(MockTestComponet)
await vm.getMsg('fuck');
} catch (err) {
expect(err.message).toEqual('获取问候语失败')
}
})
})
结束语
本单元测试的源码放在了我的github上( 点击前往 ),欢迎小伙伴点星。 如果有不当之处,欢迎赐教。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。