根據標題,這有可能嗎?
我正在嘗試測試Vue應用程式的一個簡單元件,其中包含一個標題和一個按鈕,該按鈕將使用者重定向到下一個頁面。我想測試一下,當按鈕被點擊時,是否會向路由器發送事件/訊息,並檢查目標路由是否正確。
應用程式的架構如下:
App.Vue - 入口元件
<script setup lang="ts"> import { RouterView } from "vue-router"; import Wrapper from "@/components/Wrapper.vue"; import Container from "@/components/Container.vue"; import HomeView from "@/views/HomeView.vue"; </script> <template> <Wrapper> <Container> <RouterView/> </Container> </Wrapper> </template> <style> body{ @apply bg-slate-50 text-slate-800 dark:bg-slate-800 dark:text-slate-50; } </style>
router/index.ts - Vue Router設定檔
import {createRouter, createWebHistory} from "vue-router"; import HomeView from "@/views/HomeView.vue"; export enum RouteNames { HOME = "home", GAME = "game", } const routes = [ { path: "/", name: RouteNames.HOME, component: HomeView, alias: "/home" }, { path: "/game", name: RouteNames.GAME, component: () => import("@/views/GameView.vue"), }, ]; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }); export default router;
HomeView.Vue - App元件中RouterView的入口視圖
<template> <Header title="My App"/> <ButtonRouterLink :to="RouteNames.GAME" text="Start" class="btn-game"/> </template> <script setup> import Header from "@/components/Header.vue"; import ButtonRouterLink from "@/components/ButtonRouterLink.vue"; import {RouteNames} from "@/router/"; </script>
ButtonRouterLink.vue
<template> <RouterLink v-bind:to="{name: to}" class="btn"> {{ text }} </RouterLink> </template> <script setup> import { RouterLink } from "vue-router"; const props = defineProps({ to: String, text: String }) </script>
考慮到它使用CompositionAPI和TypeScript,有沒有辦法在Vitest測試中模擬路由器?
這是一個測試檔案結構的範例(HomeView.spec.ts):
import {shallowMount} from "@vue/test-utils"; import { describe, test, vi, expect } from "vitest"; import { RouteNames } from "@/router"; import HomeView from "../../views/HomeView.vue"; describe("Home", () => { test ('navigates to game route', async () => { // Check if the button navigates to the game route const wrapper = shallowMount(HomeView); await wrapper.find('.btn-game').trigger('click'); expect(MOCK_ROUTER.push).toHaveBeenCalledWith({ name: RouteNames.GAME }); }); });
我嘗試了多種方法:vi.mock('vue-router'),vi.spyOn(useRoute,'push'),vue-router-mock庫,但是我無法讓測試運行起來,似乎路由器就是不可用的。
根據@tao 的建議,我意識到在HomeView中測試點擊事件並不是一個很好的測試(測試的是庫而不是我的應用程式),所以我添加了一個測試ButtonRouterLink,看看它是否正確渲染了Vue RouterLink,使用to參數:
import {mount, shallowMount} from "@vue/test-utils"; import { describe, test, vi, expect } from "vitest"; import { RouteNames } from "@/router"; import ButtonRouterLink from "../ButtonRouterLink.vue"; vi.mock('vue-router'); describe("ButtonRouterLink", () => { test (`correctly transforms 'to' param into a router-link prop`, async () => { const wrapper = mount(ButtonRouterLink, { props: { to: RouteNames.GAME } }); expect(wrapper.html()).toMatchSnapshot(); }); });
它渲染了一個空的HTML字串""
exports[`ButtonRouterLink > correctly transforms 'to' param into a router-link prop 1`] = `""`;
伴隨著一個不太有用的Vue警告(未指定預期類型):
[Vue warn]: Invalid prop: type check failed for prop "to". Expected , got Object at <RouterLink to= { name: 'game' } class="btn btn-blue" > at <ButtonRouterLink to="game" ref="VTU_COMPONENT" > at <VTUROOT> [Vue warn]: Invalid prop: type check failed for prop "ariaCurrentValue". Expected , got String with value "page". at <RouterLink to= { name: 'game' } class="btn btn-blue" > at <ButtonRouterLink to="game" ref="VTU_COMPONENT" > at <VTUROOT> [Vue warn]: Component is missing template or render function. at <RouterLink to= { name: 'game' } class="btn btn-blue" > at <ButtonRouterLink to="game" ref="VTU_COMPONENT" > at <VTUROOT>
這是可能的。
你的範例和官方範例之間有一個重要的區別:在被測試的元件中,你將其放置在了一個中間元件中,而不是使用
RouterLink
(或使用router.push
的按鈕)。這會如何改變單元測試?
第一個問題是你使用了
shallowMount
,它會用空容器取代子元件。在你的情況下,這意味著ButtonRouterLink
不再包含RouterLink
,並且不會對點擊事件做任何操作,它只是接收點擊事件。解決這個問題的選項有:
mount
而不是shallowMount
,將其變成整合測試而不是單元測試ButtonRouterLink
的測試中。一旦測試了ButtonRouterLink
的任何:to
是否正確轉換為{ name: to }
並傳遞給RouterLink
,你只需要測試這些:to
是否正確傳遞給使用它們的任何元件中的<ButtonRouterLink />
。將這個功能移到自己的元件中所創建的第二個問題稍微微妙一些。通常,我期望以下ButtonRouterLink的測試可以工作:
令我驚訝的是,這個測試失敗了,它聲稱在RouterLinkStub中接收到的
:to
屬性值不是{ name: RouterNames.GAME }
,而是'game'
,這是我傳給ButtonRouterLink
的值。就像我們的元件從未將value
轉換為{ name: 值 }
一樣。相當奇怪。
事實證明,問題是
<RouterLink />
恰好是我們組件的根元素。這表示元件(<BottomRouterLink>
)在DOM中被<RouterLinkStub>
取代,但:to
的值是從前者而不是後者讀取的。簡而言之,如果模板是這樣的,測試就會成功:為了解決這個問題,我們需要導入實際的
RouterLink
元件(來自vue-router
),並找到它,而不是找到RouterLinkStub
。為什麼?@vue/test-utils
足夠聰明,可以找到存根元件而不是實際元件,但是這樣,.findComponent()
只會匹配替換後的元素,而不是在它仍然是ButtonRouterLink
(未處理)時。此時,:to
的值已經被解析為RouterLink
實際接收的值。完整的測試如下:
有點棘手。