1、mock 全局组件的实现
如果组件使用了全局组件,我们需要先mock一下,通过 mount
的 global
选项,注册全局的组件。
这里也可以使用组件实例,但是单元测试的重要思想是隔离,至于其他组件内部怎么实现的,让他自己的单元测试去管理就好了。
例如下面这个组件:
<template>
<router-link to="/login" v-if="!user.isLogin">
<a-button
type="primary"
class="user-profile-component"
>
登录
</a-button>
</router-link>
<div v-else>
<a-dropdown-button class="user-profile-component">
<router-link to="/setting">{{user.data.nickName}}</router-link>
<template v-slot:overlay>
<a-menu class="user-profile-dropdown">
<a-menu-item key="0" @click="createDesign">创建作品</a-menu-item>
<a-menu-item key="1"><router-link to="/works" >我的作品</router-link></a-menu-item>
<a-menu-item key="2" @click="logout">登出</a-menu-item>
</a-menu>
</template>
</a-dropdown-button>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import axios from 'axios'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import { UserProps } from '../store/user'
export default defineComponent({
name: 'user-profile',
props: {
user: {
type: Object as PropType<UserProps>,
required: true
}
},
setup () {
const store = useStore()
const router = useRouter()
const createDesign = async () => {
const payload = {
title: '未命名作品',
desc: '未命名作品',
coverImg: 'http://typescript-vue.oss-cn-beijing.aliyuncs.com/vue-marker/5f81cca3f3bf7a0e1ebaf885.png'
}
const postData = {
method: 'post', data: payload, opName: 'createDesign'
} as any
const { data } = await axios('/works', postData)
message.success('创建作品成功', 2)
router.push(`/editor/${data.data.id}`)
}
const logout = () => {
store.commit('logout')
message.success('退出登录成功,2秒后跳转到首页', 2)
setTimeout(() => {
router.push('/')
}, 2000)
}
return {
logout,
createDesign
}
}
})
</script>
1、使用了很多全局注册的组件,比如router-link
、a-button
、a-dropdown-button
这些,jest测试时会报错,因为这些组件没有注册就直接使用了。
console.warn node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:40
[Vue warn]: Failed to resolve component: router-link
at <UserProfile user= { isLogin: false } ref="VTU_COMPONENT" >
at <VTUROOT>
解决:需要在mout的时候注册全局组件。
const mockComponent = {
template:'<div><slot></slot></div>'
}
wrapper = mount(UserProfile, {
propsData: {
user: {
isLogin:false
}
},
global: {
components: {
'a-button': mockComponent,
'a-dropdown-button': mockComponent,
'router-link': mockComponent,
'a-menu': mockComponent,
'a-menu-item':mockComponent,
}
}
})
2、这个组件使用了vue-router
、ant-design-vue
、axios
和vuex
这些第三方库,在单元测试得把他们mock一下。
这里可以理解下,jest先执行了mock的代码,后面测试用例的代码执行时候引用了这些第三方库的时候,就会利索当然的用到的mock的第三方库。
jest.mock('ant-design-vue')
jest.mock('vuex')
jest.mock('vue-router')
模拟第三方库实现的三种方法
测试行为
- ant-design-vue - message - success()
- vue-router - useRouter() - push()
- vuex - useStore() - commit()
方法一:模拟实现
我们先引用了 message 模块,代码运行时,jest.mock 会先执行,模拟的模块会覆盖 message 真实模块。
只需要确定message方法被调用就好了。 ```javascript import { message } from “ant-design-vue”;
// 回调函数,返回模块的实现 jest.mock(“ant-design-vue”, () => ({ message: { success: jest.fn(), }, }));
expect(message.success).toHaveBeenCalled();
<a name="yu8lR"></a>
## 方法二:挂载全局实例
在执行之前,全局通过 `provide` 选项,挂载了真实的store.。所以`jest.mock('vuex')`要去掉
```javascript
import store from "@/store/index";
beforeAll(() => {
wrapper = mount(UserProfile, {
props: {
user: { isLogin: false },
},
global: {
components: globalComponents,
// https://github.com/vuejs/vue-test-utils-next/issues/196
provide: {
store, // 挂载实例
},
},
});
});
expect(store.state.user.userName).toBe("viking");
方法三:半真半假实现
通过模拟 vue-router
的 push
方法,如果数组中有添加对应的路由,那么说明方法调用成功
const mockedRoutes: string[] = []
jest.mock("vue-router", () => ({
useRouter: () => ({
push: (url: string) => mockedRoutes.push(url)
})
}));
it("should call logout and show message, call router.push after timeout", async () => {
await wrapper.get(".user-profile-dropdown div").trigger("click");
expect(store.state.user.isLogin).toBeFalsy();
expect(message.success).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(mockedRoutes).toEqual(["/"]);
});