浅谈vue单元测试最佳实践(中)
三、开始单元测试1、整合的官方demo
//src\components\hCounter\counter.vue
<template>
<div>
<span class="desc">{{ desc }}</span>
<span class="count">{{ count }}</span>
<button @click="increment">Increment</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
props: {
desc: {
type: String,
default: "Counter"
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
2、编写单元测试用例
//src\components\__test__\counter.spec.js
import { mount } from '@vue/test-utils'
import Counter from '@/components/hCounter/counter';
describe('Counter', () => {
// 现在挂载组件,你便得到了这个包裹器
const wrapper = mount(Counter, {
propsData: {
desc: 'I am a counter'
}
})
console.log('wrapper.vm.desc' , wrapper.vm.desc)
console.log('wrapper.vm.count' , wrapper.vm.count)
console.log('options.attachedToDocument' , wrapper.options.attachedToDocument)
// console.log('wrapper.element' , wrapper.element)
it('renders the desc', () => {
expect(wrapper.html()).toContain('I am a counter')
});
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<span class="count">0</span>')
})
// 也便于检查已存在的元素
it('has a button', () => {
expect(wrapper.find('button').exists()).toBe(true)
// console.log(expect(wrapper.find('button')))
// expect(wrapper.contains('button')).toBe(true)
})
//模拟用户交互
//用户点击按钮则计数器增加递增,首先通过wrapper.find()定位改按钮,改方法返回一个该按钮元素的包裹器,然后对按钮包裹器调用.trigger模拟操作
it('button click should increment the count', () => {
expect(wrapper.vm.count).toBe(0)
const button = wrapper.find('button')
button.trigger('click')
expect(wrapper.vm.count).toBe(1)
})
it('set count to 100', async () => {
await wrapper.setData({'count': 100});
expect(wrapper.vm.count).toBe(100)
})
//为了测试计数器中的文本是否更新,需要使用异步nextTick await
//任何导致操作DOM的改变都应该在断言之前await
it('button click should increment the count text', async () => {
expect(wrapper.text()).toContain('1')
const button = wrapper.find('button')
await button.trigger('click')
expect(wrapper.text()).toContain('2')
})
})
3、对UI单元测试的建议
对于 UI 组件来说,我们不推荐一味追求行级覆盖率,因为它会导致我们过分关注组件的内部实现细节,从而导致琐碎的测试。
取而代之的是,我们推荐把测试撰写为断言你的组件的公共接口,并在一个黑盒内部处理它。一个简单的测试用例将会断言一些输入 (用户的交互或 prop 的改变) 提供给某组件之后是否导致预期结果 (渲染结果或触发自定义事件)。
比如,对于每次点击按钮都会将计数加一的 Counter 组件来说,其测试用例将会模拟点击并断言渲染结果会加 1。该测试并没有关注 Counter 如何递增数值,而只关注其输入和输出。
该提议的好处在于,即便该组件的内部实现已经随时间发生了改变,只要你的组件的公共接口始终保持一致,测试就可以通过。
4、某项目首页tab的单元测试
大致的逻辑上是,未登录的时候有4个tab分别是主页、产品介绍、案例展示、联系我们。
登陆之前:
http://www.51testing.com/attachments/2023/06/15326880_2023060915045718RZ6.jpg
登陆之后从home页返回之后会把主页去掉,新的tab是返回系统、产品介绍、业务指南、培训视频、案例展示、联系我们,且点击返回系统会根据角色跳转到不同的页面。
http://www.51testing.com/attachments/2023/06/15326880_202306091505001tSkK.jpg
完整的单元测试用例如下~
import { mount, createLocalVue } from '@vue/test-utils'
import loginHeader from '@/components/loginHeader'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
const router = new VueRouter()
describe('loginHeader',() => {
const wrapperNotLogin = mount(loginHeader, {
localVue,
router
});
it('未登录之前的tab展示', () => {
const loginTabWrap = wrapperNotLogin.find('.login-tab-wrap');
const tabsArr = ['主页', '产品介绍', '案例展示', '联系我们'];
for(let i = 1; i < 5; i++) {
const findDom = loginTabWrap.findAll('div').at(i);
expect(findDom.html()).toContain(tabsArr)
}
});
//模拟登陆之后的情况
sessionStorage.setItem('objUser', 'yes')
sessionStorage.setItem('system.route.path.type', 'PLAN_MANAGER')
const wrapperLogin = mount(loginHeader, {
mocks: {
$route: {//伪造路由
path: '/home'
},
$router: {}
}
});
it('登陆成功且是计划管理人身份', () => {
const loginTabWrap = wrapperLogin.find('.login-tab-wrap');
const tabsArr = ['返回系统', '产品介绍', '业务指南', '培训视频', '案例展示', '联系我们'];
for(let i = 1; i < 7; i++) {
const findDom = loginTabWrap.findAll('div').at(i);
expect(findDom.html()).toContain(tabsArr)
}
});
it('when system.route.path.typ == PLAN_MANAGER tabsArr.toRouter == /absProductManage/index?active=1', () => {
expect(wrapperLogin.vm.tabArr.toRouter).toBe('/absProductManage/index?active=1')
});
})
npm run test之后的结果如下,通过了单元测试。
http://www.51testing.com/attachments/2023/06/15326880_202306091505041xNR3.jpg
页:
[1]