视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
对 Vue-Router 进行单元测试的方法
2020-11-27 22:04:29 责编:小采
文档

来更新一下 <NestedRoute>:

<template>
 <div>
 Nested Route
 <div class="username">
 {{ $route.params.username }}
 </div>
 </div>
</template>

现在报错变为了:

tests/unit/NestedRoute.spec.js
  NestedRoute
    ✕ renders a username from query string (17ms)

  ● NestedRoute › renders a username from query string

    TypeError: Cannot read property 'params' of undefined

这是因为 $route  并不存在。 我们当然可以用一个真正的路由,但在这样的情况下只用一个 mocks  加载选项会更容易些:

it("renders a username from query string", () => {
 const username = "alice"
 const wrapper = shallowMount(NestedRoute, {
 mocks: {
 $route: {
 params: { username }
 }
 }
 })

 expect(wrapper.find(".username").text()).toBe(username)
})

这样测试就能通过了。在本例中,我们没有做任何的导航或是和路由的实现相关的任何其他东西,所以 mocks  就挺好。我们并不真的关心 username  是从查询字符串中怎么来的,只要它出现就好。

测试路由钩子的策略

Vue Router 提供了多种类型的路由钩子, 称为 “navigation guards”。举两个例子如:

  • 全局 guards (router.beforeEach)。在 router 实例上声明
  • 组件内 guards,比如 beforeRouteEnter。在组件中声明
  • 要确保这些运作正常,一般是集成测试的工作,因为需要一个使用者从一个理由导航到另一个。但也可以用单元测试检验导航 guards 中调用的函数是否正常工作,并更快的获得潜在错误的反馈。这里列出一些如何从导航 guards 中解耦逻辑的策略,以及为此编写的单元测试。

    全局 guards

    比方说当路由中包含 shouldBustCache  元数据的情况下,有那么一个 bustCache  函数就应该被调用。路由可能长这样:

    //routes.js
    
    import NestedRoute from "@/components/NestedRoute.vue"
    
    export default [
     {
     path: "/nested-route",
     component: NestedRoute,
     meta: {
     shouldBustCache: true
     }
     }
    ]
    
    

    之所以使用 shouldBustCache  元数据,是为了让缓存无效,从而确保用户不会取得旧数据。一种可能的实现如下:

    //router.js
    
    import Vue from "vue"
    import VueRouter from "vue-router"
    import routes from "./routes.js"
    import { bustCache } from "./bust-cache.js"
    
    Vue.use(VueRouter)
    
    const router = new VueRouter({ routes })
    
    router.beforeEach((to, from, next) => {
     if (to.matched.some(record => record.meta.shouldBustCache)) {
     bustCache()
     }
     next()
    })
    
    export default router
    
    

    在单元测试中,你可能想导入 router 实例,并试图通过 router.beforeHooks[0]()  的写法调用 beforeEach;但这将抛出一个关于  next  的错误 -- 因为没法传入正确的参数。针对这个问题,一种策略是在将 beforeEach  导航钩子耦合到路由中之前,解耦并单独导出它。做法是这样的:

    //router.js
    
    export function beforeEach((to, from, next) {
     if (to.matched.some(record => record.meta.shouldBustCache)) {
     bustCache()
     }
     next()
    }
    
    router.beforeEach((to, from, next) => beforeEach(to, from, next))
    
    export default router
    
    

    再写测试就容易了,虽然写起来有点长:

    import { beforeEach } from "@/router.js"
    import mockModule from "@/bust-cache.js"
    
    jest.mock("@/bust-cache.js", () => ({ bustCache: jest.fn() }))
    
    describe("beforeEach", () => {
     afterEach(() => {
     mockModule.bustCache.mockClear()
     })
    
     it("busts the cache when going to /user", () => {
     const to = {
     matched: [{ meta: { shouldBustCache: true } }]
     }
     const next = jest.fn()
    
     beforeEach(to, undefined, next)
    
     expect(mockModule.bustCache).toHaveBeenCalled()
     expect(next).toHaveBeenCalled()
     })
    
     it("busts the cache when going to /user", () => {
     const to = {
     matched: [{ meta: { shouldBustCache: false } }]
     }
     const next = jest.fn()
    
     beforeEach(to, undefined, next)
    
     expect(mockModule.bustCache).not.toHaveBeenCalled()
     expect(next).toHaveBeenCalled()
     })
    })
    
    

    最主要的有趣之处在于,我们借助 jest.mock,mock 掉了整个模块,并用 afterEach  钩子将其复原。通过将 beforeEach  导出为一个已结耦的、普通的 Javascript 函数,从而让其在测试中不成问题。

    为了确定 hook 真的调用了 bustCache  并且显示了最新的数据,可以使用一个诸如 Cypress.io 的端到端测试工具,它也在应用脚手架 vue-cli  的选项中提供了。

    组件 guards

    一旦将组件 guards 视为已结耦的、普通的 Javascript 函数,则它们也是易于测试的。假设我们为 <NestedRoute>  添加了一个 beforeRouteLeave  hook:

    //NestedRoute.vue
    
    <script>
    import { bustCache } from "@/bust-cache.js"
    export default {
     name: "NestedRoute",
     beforeRouteLeave(to, from, next) {
     bustCache()
     next()
     }
    }
    </script>
    
    

    对在全局 guard 中的方法照猫画虎就可以测试它了:

    // ...
    import NestedRoute from "@/compoents/NestedRoute.vue"
    import mockModule from "@/bust-cache.js"
    
    jest.mock("@/bust-cache.js", () => ({ bustCache: jest.fn() }))
    
    it("calls bustCache and next when leaving the route", () => {
     const next = jest.fn()
     NestedRoute.beforeRouteLeave(undefined, undefined, next)
    
     expect(mockModule.bustCache).toHaveBeenCalled()
     expect(next).toHaveBeenCalled()
    })
    
    

    这样的单元测试行之有效,可以在开发过程中立即得到反馈;但由于路由和导航 hooks 常与各种组件互相影响以达到某些效果,也应该做一些集成测试以确保所有事情如预期般工作。

    总结

    本文讲述了:

  • 测试由 Vue Router 条件渲染的组件
  • 用 jest.mock  和 localVue  去 mock Vue 组件
  • 从 router 中解耦全局导航 guard 并对其测试
  • 用 jest.mock  来 mock 一个模块
  • 下载本文
    显示全文
    专题