Router 进行单元测试的方法

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

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

使用一个真正的 router 实例

mock 掉 $route  和 $router  全局对象

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

创建组件

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

<template> <div> <router-view /> </div> </template> <script> export default { name: 'app' } </script>

<NestedRoute>  同样迷你:

<template> <div>Nested Route</div> </template> <script> export default { name: "NestedRoute" } </script>

现在定义一个路由:

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

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

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:

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>。使用了下面的写法后,以上测试也将能通过:

jest.mock("@/components/NestedRoute.vue", () => ({ name: "NestedRoute", render: h => h("div") }))

使用 Mock Router

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

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>  ,所以一运行测试就会报错:

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>:

<template> <div> Nested Route <div> {{ $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  加载选项会更容易些:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/a4e3c020dfc02353521c9bcea3a37db3.html