根据标题,这有可能吗?
我正在尝试测试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: value }
一样。相当奇怪。
事实证明,问题是
<RouterLink />
恰好是我们组件的根元素。这意味着组件(<BottomRouterLink>
)在DOM中被<RouterLinkStub>
替换,但:to
的值是从前者而不是后者读取的。简而言之,如果模板是这样的,测试就会成功:为了解决这个问题,我们需要导入实际的
RouterLink
组件(来自vue-router
),并找到它,而不是找到RouterLinkStub
。为什么?@vue/test-utils
足够聪明,可以找到存根组件而不是实际组件,但是这样,.findComponent()
只会匹配替换后的元素,而不是在它仍然是ButtonRouterLink
(未处理)时。此时,:to
的值已经被解析为RouterLink
实际接收的值。完整的测试如下:
有点棘手。