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: