1、mock 全局组件的实现

如果组件使用了全局组件,我们需要先mock一下,通过 mountglobal 选项,注册全局的组件。
这里也可以使用组件实例,但是单元测试的重要思想是隔离,至于其他组件内部怎么实现的,让他自己的单元测试去管理就好了。
例如下面这个组件:

  1. <template>
  2. <router-link to="/login" v-if="!user.isLogin">
  3. <a-button
  4. type="primary"
  5. class="user-profile-component"
  6. >
  7. 登录
  8. </a-button>
  9. </router-link>
  10. <div v-else>
  11. <a-dropdown-button class="user-profile-component">
  12. <router-link to="/setting">{{user.data.nickName}}</router-link>
  13. <template v-slot:overlay>
  14. <a-menu class="user-profile-dropdown">
  15. <a-menu-item key="0" @click="createDesign">创建作品</a-menu-item>
  16. <a-menu-item key="1"><router-link to="/works" >我的作品</router-link></a-menu-item>
  17. <a-menu-item key="2" @click="logout">登出</a-menu-item>
  18. </a-menu>
  19. </template>
  20. </a-dropdown-button>
  21. </div>
  22. </template>
  23. <script lang="ts">
  24. import { defineComponent, PropType } from 'vue'
  25. import axios from 'axios'
  26. import { useStore } from 'vuex'
  27. import { useRouter } from 'vue-router'
  28. import { message } from 'ant-design-vue'
  29. import { UserProps } from '../store/user'
  30. export default defineComponent({
  31. name: 'user-profile',
  32. props: {
  33. user: {
  34. type: Object as PropType<UserProps>,
  35. required: true
  36. }
  37. },
  38. setup () {
  39. const store = useStore()
  40. const router = useRouter()
  41. const createDesign = async () => {
  42. const payload = {
  43. title: '未命名作品',
  44. desc: '未命名作品',
  45. coverImg: 'http://typescript-vue.oss-cn-beijing.aliyuncs.com/vue-marker/5f81cca3f3bf7a0e1ebaf885.png'
  46. }
  47. const postData = {
  48. method: 'post', data: payload, opName: 'createDesign'
  49. } as any
  50. const { data } = await axios('/works', postData)
  51. message.success('创建作品成功', 2)
  52. router.push(`/editor/${data.data.id}`)
  53. }
  54. const logout = () => {
  55. store.commit('logout')
  56. message.success('退出登录成功,2秒后跳转到首页', 2)
  57. setTimeout(() => {
  58. router.push('/')
  59. }, 2000)
  60. }
  61. return {
  62. logout,
  63. createDesign
  64. }
  65. }
  66. })
  67. </script>

1、使用了很多全局注册的组件,比如router-linka-buttona-dropdown-button这些,jest测试时会报错,因为这些组件没有注册就直接使用了。

  1. console.warn node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:40
  2. [Vue warn]: Failed to resolve component: router-link
  3. at <UserProfile user= { isLogin: false } ref="VTU_COMPONENT" >
  4. at <VTUROOT>

解决:需要在mout的时候注册全局组件。

  1. const mockComponent = {
  2. template:'<div><slot></slot></div>'
  3. }
  4. wrapper = mount(UserProfile, {
  5. propsData: {
  6. user: {
  7. isLogin:false
  8. }
  9. },
  10. global: {
  11. components: {
  12. 'a-button': mockComponent,
  13. 'a-dropdown-button': mockComponent,
  14. 'router-link': mockComponent,
  15. 'a-menu': mockComponent,
  16. 'a-menu-item':mockComponent,
  17. }
  18. }
  19. })

2、这个组件使用了vue-routerant-design-vueaxiosvuex这些第三方库,在单元测试得把他们mock一下。

这里可以理解下,jest先执行了mock的代码,后面测试用例的代码执行时候引用了这些第三方库的时候,就会利索当然的用到的mock的第三方库。

  1. jest.mock('ant-design-vue')
  2. jest.mock('vuex')
  3. 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();

  1. <a name="yu8lR"></a>
  2. ## 方法二:挂载全局实例
  3. 在执行之前,全局通过 `provide` 选项,挂载了真实的store.。所以`jest.mock('vuex')`要去掉
  4. ```javascript
  5. import store from "@/store/index";
  6. beforeAll(() => {
  7. wrapper = mount(UserProfile, {
  8. props: {
  9. user: { isLogin: false },
  10. },
  11. global: {
  12. components: globalComponents,
  13. // https://github.com/vuejs/vue-test-utils-next/issues/196
  14. provide: {
  15. store, // 挂载实例
  16. },
  17. },
  18. });
  19. });
  20. expect(store.state.user.userName).toBe("viking");

方法三:半真半假实现

通过模拟 vue-routerpush 方法,如果数组中有添加对应的路由,那么说明方法调用成功

  1. const mockedRoutes: string[] = []
  2. jest.mock("vue-router", () => ({
  3. useRouter: () => ({
  4. push: (url: string) => mockedRoutes.push(url)
  5. })
  6. }));
  7. it("should call logout and show message, call router.push after timeout", async () => {
  8. await wrapper.get(".user-profile-dropdown div").trigger("click");
  9. expect(store.state.user.isLogin).toBeFalsy();
  10. expect(message.success).toHaveBeenCalledTimes(1);
  11. jest.runAllTimers();
  12. expect(mockedRoutes).toEqual(["/"]);
  13. });