Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[译] 对 Vue-Router 进行单元测试

[译] 对 Vue-Router 进行单元测试

作者头像
江米小枣
发布于 2020-06-15 13:45:16
发布于 2020-06-15 13:45:16
2.3K00
代码可运行
举报
文章被收录于专栏:云前端云前端
运行总次数:0
代码可运行

原文:https://medium.com/js-dojo/unit-testing-vue-router-1d091241312

由于路由通常会把多个组件牵扯到一起操作,所以一般对其的测试都在 端到端/集成 阶段进行,处于测试金字塔的上层。不过,做一些路由的单元测试还是大有益处的。

对于与路由交互的组件,有两种测试方式:

  • 使用一个真正的 router 实例
  • mock 掉 $route$router 全局对象

因为大多数 Vue 应用用的都是官方的 Vue Router,所以本文会谈谈这个。

创建组件

我们会弄一个简单的 <App>,包含一个 /nested-child 路由。访问 /nested-child 则渲染一个 <NestedRoute> 组件。创建 App.vue 文件,并定义如下的最小化组件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
 <div id="app">
   <router-view />
 </div>
</template><script>
export default {
 name: 'app'
}
</script>

<NestedRoute> 同样迷你:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
 <div>Nested Route</div>
</template><script>
export default {
 name: "NestedRoute"
}
</script>

现在定义一个路由:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import NestedRoute from "@/components/NestedRoute.vue"export default [
 { path: "/nested-route", component: NestedRoute }
]

在真实的应用中,一般会创建一个 router.js 文件并导入定义好的路由,写出来一般是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import Vue from "vue"
import VueRouter from "vue-router"
import routes from "./routes.js"Vue.use(VueRouter)export default new VueRouter({ routes })

为避免调用 Vue.use(...) 污染测试的全局命名空间,我们将会在测试中创建基础的路由;这让我们能在单元测试期间更细粒度的控制应用的状态。

编写测试

先看点代码再说吧。我们来测试 App.vue,所以相应的增加一个 App.spec.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { shallowMount, mount, createLocalVue } from "@vue/test-utils"
import App from "@/App.vue"
import VueRouter from "vue-router"
import NestedRoute from "@/components/NestedRoute.vue"
import routes from "@/routes.js"

const localVue = createLocalVue()
localVue.use(VueRouter)

describe("App", () => {
  it("renders a child component via routing", () => {
    const router = new VueRouter({ routes })
    const wrapper = mount(App, { localVue, router })

    router.push("/nested-route")

    expect(wrapper.find(NestedRoute).exists()).toBe(true)
  })
})

照例,一开始先把各种模块引入我们的测试;尤其是引入了应用中所需的真实路由。这在某种程度上很理想 -- 若真实路由一旦挂了,单元测试就失败,这样我们就能在部署应用之前修复这类问题。

可以在 <App> 测试中使用一个相同的 localVue,并将其声明在第一个 describe 块之外。而由于要为不同的路由做不同的测试,所以把 router 定义在 it 块里。

另一个要注意的是这里用了 mount 而非 shallowMount。如果用了 shallowMount,则 <router-link> 就会被忽略,不管当前路由是什么,渲染的其实都是一个无用的替身组件。

为使用了 mount 的大型渲染树做些变通

使用 mount 在某些情况下很好,但有时却是不理想的。比如,当渲染整个 <App> 组件时,正赶上渲染树很大,包含了许多组件,一层层的组件又有自己的子组件。这么些个子组件都要触发各种生命周期钩子、发起 API 请求什么的。

如果你在用 Jest,其强大的 mock 系统为此提供了一个优雅的解决方法。可以简单的 mock 掉子组件,在本例中也就是 <NestedRoute>。使用了下面的写法后,以上测试也将能通过:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
jest.mock("@/components/NestedRoute.vue", () => ({
 name: "NestedRoute",
 render: h => h("div")
}))

使用 Mock Router

有时真实路由也不是必要的。现在升级一下 <NestedRoute>,让其根据当前 URL 的查询字符串显示一个用户名。这次我们用 TDD 实现这个特性。以下是一个基础测试,简单的渲染了组件并写了一句断言:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { shallowMount } from "@vue/test-utils"
import NestedRoute from "@/components/NestedRoute.vue"
import routes from "@/routes.js"

describe("NestedRoute", () => {
  it("renders a username from query string", () => {
    const username = "alice"
    const wrapper = shallowMount(NestedRoute)

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

然而我们并没有 <div class="username"> ,所以一运行测试就会报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
tests/unit/NestedRoute.spec.js
  NestedRoute
    ✕ renders a username from query string (25ms)

  ● NestedRoute › renders a username from query string

    [vue-test-utils]: find did not return .username, cannot call text() on empty Wrapper

来更新一下 <NestedRoute>

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
 <div>
   Nested Route
   <div class="username">
     {{ $route.params.username }}
   </div>
 </div>
</template>

现在报错变为了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 加载选项会更容易些:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 函数就应该被调用。路由可能长这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//routes.jsimport NestedRoute from "@/components/NestedRoute.vue"export default [
 {
   path: "/nested-route",
   component: NestedRoute,
   meta: {
     shouldBustCache: true
   }
 }
]

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//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 导航钩子耦合到路由中之前,解耦并单独导出它。做法是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//router.jsexport 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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//NestedRoute.vue<script>
import { bustCache } from "@/bust-cache.js"
export default {
 name: "NestedRoute",
 beforeRouteLeave(to, from, next) {
   bustCache()
   next()
 }
}
</script>

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ...
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.mocklocalVue 去 mock Vue 组件
  • 从 router 中解耦全局导航 guard 并对其独立测试
  • jest.mock 来 mock 一个模块
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云前端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
[译] Vue Router 之单元测试
原文:https://github.com/tonylua/vue-testing-handbook/blob/master/src/zh-CN/vue-router.md
江米小枣
2020/06/15
2K0
前端单元测试那些事
Jest 是 Facebook 开源的一款 JS 单元测试框架,它也是 React 目前使用的单元测试框架,目前vue官方也把它当作为单元测试框架官方推荐 。 目前除了 Facebook 外,Twitter、Airbnb 也在使用 Jest。Jest 除了基本的断言和 Mock 功能外,还有快照测试、实时监控模式、覆盖度报告等实用功能。 同时 Jest 几乎不需要做任何配置便可使用。
树酱
2020/07/03
4.6K0
前端单元测试那些事
[译] Vuex 之单元测试
原文:https://lmiller1990.github.io/vue-testing-handbook/testing-vuex.html
江米小枣
2020/06/15
3.4K0
【架构师(第三十篇)】Vue-Test-Utils 全局组件和第三方库 vuex | vue-router
此时会出现类似于 Failed to resolve component: a-button 的报错
一尾流莺
2022/12/10
2.3K0
【架构师(第三十篇)】Vue-Test-Utils 全局组件和第三方库 vuex | vue-router
Vue 应用单元测试的策略与实践 04 - Vuex 单元测试
2.2 在 Vue 应用的单元测试中,对 Vuex store 该如何测试?如何测试与 Vue 组件之间的交互?
JimmyLv_吕靖
2019/09/10
1.7K0
Vue 应用单元测试的策略与实践 04 - Vuex 单元测试
Vue 业务系统如何落地单元测试
一直对单测很感兴趣,但对单测覆盖率、测试报告等关键词懵懵懂懂,最近几个月一直在摸索如何在Vue业务系统中落地单元测试,看到慢慢增长的覆盖率,慢慢清晰的模块,对单元测试的理解也比以前更加深入,也有一些心得和收获。
coder_koala
2021/07/12
4K0
Vue 应用单元测试的策略与实践 02 - 单元测试基础
在上一篇文章当中我们介绍了单元测试的意义,以及为何选择 Facebook 的 Jest 作为我们的测试框架。现在就让我们一起来学习如何编写最基础的单元测试。
JimmyLv_吕靖
2019/09/10
2.3K0
Vue 应用单元测试的策略与实践 02 - 单元测试基础
React + Redux Testing Library 单元测试
谈任何东西都一定要有个上下文。你的论述不能是「因为单元测试有这些好处,所以我们要做单元测试」,而应该是「不做单元测试我们会遇到什么问题」,这样才能回答「为什么要写单元测试」的问题。那么我们谈论单元测试的上下文是什么呢?不做单元测试我们会遇到什么问题呢?上图为一个产品从 idea 分析、设计、开发、测试到交付并获取市场反馈的过程。
JimmyLv_吕靖
2021/03/03
2.5K0
React + Redux Testing Library 单元测试
【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求
---- 知识点 将 mock 对象断言为特定类型 使用 jest.Mocked<T> 使用 it.only 来指定测试的 case 使用 skip 跳过指定测试的 case 测试内容 触发事件 trigger 方法 测试界面是否更新 特别注意 DOM 更新是个异步的过程 使用 async await 更新表单 setValue 方法 验证事件是否发送 emitted 方法 测试异步请求 模拟第三方库实现 测试准备和结束 可以使用内置的一些钩子来简化一些通用的逻辑,以下钩子用于一次性完成测试准备。 b
一尾流莺
2022/12/10
9580
【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求
实例入门 Vue.js 单元测试
作为一个以 文档丰富 而广为人知的前端开发框架, Vue.js 的官方文档中分别在《教程-工具-单元测试》、《Cookbook-Vue组件的单元测试》里对 Vue 组件的单元测试方法做出了介绍,并提供了官方的单元测试实用工具库 Vue Test Utils;甚至在状态管理工具 Vuex 的文档里也不忘留出《测试》一章。
江米小枣
2020/06/15
3K0
Vue 应用单元测试的策略与实践 03 - Vue 组件单元测试
2.1 在 Vue 应用的单元测试中,对不同 UI 组件的单元测试有何不同?颗粒度该细到什么样的程度?
JimmyLv_吕靖
2019/09/10
1.3K0
Vue 应用单元测试的策略与实践 03 - Vue 组件单元测试
那些年错过的React组件单元测试(上)
关于前端单元测试,其实两年前我就已经关注了,但那时候只是简单的知道断言,想着也不是太难的东西,项目中也没有用到,然后就想当然的认为自己就会了。
前端森林
2021/04/12
5.3K0
前端单元测试之Jest
关于前端单元测试的好处自不必说,基础的介绍和知识可以参考之前的博客链接:React Native单元测试。在软件的测试领域,测试主要分为:单元测试、集成测试和功能测试。
xiangzhihong
2022/11/30
2.9K0
单元测试
测试的目的是为了带给我们带来强大的代码信心,如果把测试初衷忘掉,会很容易掉入测试代码细节的陷阱。一旦关注点不是代码的信心,而是测试代码细节,那么测试用例会变得非常脆弱,难以维护。
用户4619307
2024/01/12
6370
vue全家桶之vue-router
vue-router默认是通过哈希路由的方式实现的。这是一种比较low的方式。 如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。 使用后对搜索引擎比较友好,好看。缺点是后端要nginx配置。
一粒小麦
2019/07/18
1.3K0
vue全家桶之vue-router
使用 Jest 进行前端单元测试
目前 Jest 已经在 Facebook 开源的 React, React Native 等前端项目中被做为标配测试框架。下面简单介绍一些 Jest 比较有用的功能和用法。
QQ音乐技术团队
2018/01/31
5.8K0
使用 Jest 进行前端单元测试
Jest单元测试之旅—实践总结
维基百科对于单元测试的定义:是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
gary12138
2022/10/05
10.6K0
Jest单元测试之旅—实践总结
React单元测试:Jest + Enzyme(二)
在上一篇教程中,我们成功搭建了基于Jest和Enzyme的单元测试框架并成功地跑起来第一个单元测试,可以点击这里回顾一下。今天,我们重点讨论如何通过Jest来mock数据。
Dickensl
2022/06/14
1.5K0
React单元测试:Jest + Enzyme(二)
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
如果使用的是 vscode 并且安装了 jest 插件,那么可以实时并且直观的看到测试是否通过
一尾流莺
2022/12/10
1.4K0
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
Vue 测试速成班
在你快要完成一个项目时,突然工程里的很多地方都出现了 bug,你修完一个又冒出新的一个,就像在玩打地鼠游戏一样……几轮下来,你会感到一团糟。
WecTeam
2020/02/11
2.9K0
Vue 测试速成班
相关推荐
[译] Vue Router 之单元测试
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验