1、mock 全局组件的实现
如果组件使用了全局组件,我们需要先mock一下,通过 mount 的 global 选项,注册全局的组件。
这里也可以使用组件实例,但是单元测试的重要思想是隔离,至于其他组件内部怎么实现的,让他自己的单元测试去管理就好了。
例如下面这个组件:
<template><router-link to="/login" v-if="!user.isLogin"><a-buttontype="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 anyconst { 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-linkat <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')`要去掉```javascriptimport store from "@/store/index";beforeAll(() => {wrapper = mount(UserProfile, {props: {user: { isLogin: false },},global: {components: globalComponents,// https://github.com/vuejs/vue-test-utils-next/issues/196provide: {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(["/"]);});
