官方测试工具
特别注意要使用版本2.0.0以上:文档地址
作用:提供特定的方法,在隔离的环境下进行组件的挂载,以及一系列测试
配置开发环境
如果在创建项目时选择了单元测试一下该步骤可以忽略
在配置之前我们先要了解vue-cli有如下特点:
- vue-cli是基于插件开发的,插件可以:
- 安装对应的依赖
- 修改内部配置
- 注入命令
- 在现有项目中安装插件
- vue add xxx
安装插件
vue add unit-jest
插件地址unit-jest
执行完成后我们会发现在项目package.json
中会多出如下依赖包,已及script脚本,并在根目录下生成tests
文件夹和jest.config.js
:
插件运行过程
- 安装的依赖
- vue-test-unit
- vue-jest
- 注入了新的命令
- vue-cli-server test:unit
- any files in
tests/unit
that end in .spec.(js|jsx|ts|tsx) - any (js(x) | ts(x))files inside
__tests__
directories
- any files in
- vue-jest转换
- 将vue SFC文件格式转换为对应的ts文件
- 将ts通过presets/typescript-babel转换成对应的js文件
开始测试
开始测试之前我们需要了解如下知识点:
- 渲染组件
mount
和shallowMount
- 传递属性
- 元素是否成功的显示
- 查找元素的不同写法
- get,getAl
- find,findAll
- findComponent,getComponent
- 触发事件
- trigger
- 测试界面是否更新
- 特别注意dom更新是一个一步过程,注意在测试中使用asyn,await 有点jquery的感觉了
测试用例
先来个简单的例子,判断一个元素的文本内容,以及触发点击事件后的处理。
判断文本内容
1.创建一个test.vue
文件,代码如下,现在我们要测试<p class="msg">{{ msg }}</p>
的内容,和触发addCount
事件后<h1 class="number">{{ count }}</h1>
的文本内容
<template>
<div>
<p class="msg">{{ msg }}</p>
<h1 class="number">{{ count }}</h1>
<button @click="addCount">ADD</button>
</div>
</template>
<script lang='ts'>
import { ref } from 'vue'
interface DataProps {}
export default {
name: 'Test',
props: {
msg: {
type: String,
default: ''
}
},
setup() {
const count = ref<number>(0)
const addCount = ()=> {
count.value++
}
return {
count,
addCount
}
}
};
</script>
接下来写测试用例,在tests/unit
下创建test.spec.ts
文件
import Test from '@/components/test.vue'
import { mount, shallowMount } from '@vue/test-utils'
describe('test.vue',()=> {
it('renders props.msg when passed',()=>{
const msg = "new msg"
// 获取到组件实例
const wrapper = mount(Test, {
// 实例中的传递的props
props: {
msg
}
})
// 获取组件的html文本
console.log(wrapper.html())
// case
expect(wrapper.get('.msg').text()).toBe(msg)
})
})
- 首先引入
Test
组件,调用@vue/test-utils
中的mount
方法,获取到test.vue
组件实例对象 - 我们希望在
<p class="msg">{{ msg }}</p>
的文本为new msg
- 启动测试命令
npm run test:unit -- --watch
,注意后面的 -- --watch是jest
自带的类似webpack的--wacth - 看一下测试的结果如下:
已经通过了,我们改一下case为expect(wrapper.get('.msg').text()).toBe('222')
,发现未通过
说明这个测试用例是ok的
接下来我们触发点击事件
触发点击事件
describe('test.vue',()=> {
it('renders props.msg when passed', ()=>{
...
// 触发点击事件
wrapper.get('.addCount').trigger('click')
expect(wrapper.get('.number').text()).toBe('1')
})
})
上面代码首先触发点击事件,然后获取期望值,因为是count.value++
此时的count应该是1
,但实际情况却如下图,难道是test.vue
代码中的addCount
没触发吗?但是运行项目发现是可以触发addCount
的,所以可能情况就是出现了异步,这个时候首先想到了async,await,代码稍加处理下如下:
describe('test.vue',()=> {
it('renders props.msg when passed', async ()=>{
...
// 触发点击事件
await wrapper.get('.addCount').trigger('click')
expect(wrapper.get('.number').text()).toBe('1')
})
})
再次查看控制台如下图:
更新表单
<template>
<div>
<p class="msg">{{ msg }}</p>
<h1 class="number">{{ count }}</h1>
<button class="addCount" @click="addCount">ADD</button>
<!-- 表单 -->
<input type="text" v-model="inputValue" />
<!-- 触发添加 -->
<button @click="addTodo" class="addTodo">添加数据</button>
<!-- 渲染列表 -->
<ul>
<li v-for="(item, index) in list" :key="index">
{{ item }}
</li>
</ul>
</div>
</template>
<script lang='ts'>
import { ref } from 'vue'
interface DataProps {}
export default {
name: 'Test',
props: {
msg: {
type: String,
default: ''
}
},
setup(props, context) {
const count = ref<number>(0)
const inputValue = ref<string>('')
const list = ref<string[]>([])
const addCount = ()=> {
count.value++
}
const addTodo = () => {
if(inputValue.value) {
list.value.push(inputValue.value)
context.emit('send', inputValue.value)
}
}
return {
count,
addCount,
inputValue,
addTodo
}
}
};
</script>
流程如下:
- 输入框输入文本内容
- 输入后点击
添加数据
按钮push到list
中 - 在页面进行渲染 开始编写测试用例:
describe('test.vue',()=> {
it('renders props.msg when passed',async ()=> {
const msg = "new msg"
const wrapper = mount(Test, {
props: {
msg
}
})
// mock一条数据
const value = 'inputValue'
// 写入到input中
await wrapper.get('input').setValue(value)
// 验证input的value input使用
expect(wrapper.get('input').element.value).toBe(value)
// 触发点击addTodo事件
await wrapper.get('.addTodo').trigger('click')
// 获取所有渲染的li的长度
expect(wrapper.findAll('li')).toHaveLength(1)
// 判断第一个li的text
expect(wrapper.get('li').text()).toBe(value)
})
})
可以看到所有的测试用例都过了如下图:
验证事件
在组件中我们会有一些发射事件的处理,先看一下代码,我们在点击按钮的发射一个send
事件,并传入
inputValue.value
export default {
name: 'Test',
// 添加send
emits: ['send'],
setup(props, context) {
...
const addTodo = () => {
if(inputValue.value) {
...
context.emit('send', inputValue.value)
}
}
....
}
};
接下来开始写测试用例:
it('renders props.msg when passed',async ()=> {
const msg = "new msg"
const wrapper = mount(Test, {
props: {
msg
}
})
...
// 判断发射事件是否存在
expect(wrapper.emitted()).toHaveProperty('send')
const events = wrapper.emitted('send')
// 判断传入的参数
expect(events ? events[0]: []).toEqual([value])
// 2. 输入后点击`添加数据`按钮push到`list`中
// 3. 在页面进行渲染
})
结果如下图,表明已通过
异步请求
<template>
<div>
...
<button class="loadUser" @click="loadUser">load</button>
<p v-if="user.loading" class="loading">Loading</p>
<div v-else class="userName">{{user.data && user.data.username}}</div>
<p v-if="user.error" class="error">error!</p>
...
</div>
</template>
<script lang="ts">
import { defineComponent, watchEffect, ref, reactive } from "vue";
import userLoader from '@/hooks/useLoader'
import axios from "axios";
import Hello from './hello.vue'
interface PostProps {
count: number,
errorCode: number,
message: string,
result: boolean,
data?: [
{
agentId: string,
agentName: string,
id: string
}
]
}
export default defineComponent({
...
setup(props,context) {
const user = reactive({
data: null as any,
loading: false,
error: false
})
const loadUser = () => {
user.loading = true
axios.get('https://jsonplaceholder.typicode.com/users/1').then(resp => {
console.log(resp)
user.data = resp.data
}).catch(() => {
user.error = true
}).finally(() => {
user.loading = false
})
}
return {
user,
loadUser
};
},
});
</script>
流程也很简单首先点击button
按钮,然后触发axios
请求,最后渲染到页面上
开始测试用例编写:
import { shallowMount, mount, VueWrapper } from '@vue/test-utils'
// 引入axios
import axios from 'axios'
// flush-promises 会清除所有等待完成的 Promise 具柄
import flushPromises from 'flush-promises'
//模拟axios
jest.mock('axios')
// 很重要的一步,断言成真正的mock类型同时保留axios的方法
const mockAxios = axios as jest.Mocked<typeof axios>
describe('axios', ()=> {
it('test axios', async ()=> {
mockAxios.get.mockResolvedValueOnce({ data: {username: 'xxxx'} })
// 点击
await wrapper.get('.loadUser').trigger('click')
// 判断请求是否被调用
expect(mockAxios.get).toHaveBeenCalled()
// 判断按钮是否显示
expect(wrapper.find('.loading').exists()).toBeTruthy()
// 界面更新完毕
await flushPromises()
// 判断请求成功后<p class="loading">Loading</p>是否存在
expect(wrapper.find('.loading').exists()).toBeFalsy()
// 判断<div class="userName"></div>最终文本是否是xxxx
expect(wrapper.get('.userName').text()).toBe('xxxx')
})
})
好了,基本的介绍就先到了。
写在最后
最后,推荐一套TS全系列的教程吧。 近期在提升TS,收藏了一套很不错的教程,无偿分享给xdm www.yidengxuetang.com/pub-page/in…