您好,登錄后才能下訂單哦!
本篇文章為大家展示了 Vue-Router如何進行單元測試,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
創建組件
我們會弄一個簡單的 <App>,包含一個 /nested-child 路由。訪問 /nested-child 則渲染一個 <NestedRoute> 組件。創建 App.vue 文件,并定義如下的最小化組件:
<template> <div id="app"> <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 class="username"> ,所以一運行測試就會報錯:
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 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() })
Vue具體輕量級框架、簡單易學、雙向數據綁定、組件化、數據和結構的分離、虛擬DOM、運行速度快等優勢,Vue中頁面使用的是局部刷新,不用每次跳轉頁面都要請求所有數據和dom,可以大大提升訪問速度和用戶體驗。
上述內容就是 Vue-Router如何進行單元測試,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。