[{"data":1,"prerenderedAt":216},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fhow-to-test-vue-router-components-with-testing-library-and-vitest":3,"surround:\u002Fposts\u002F2025\u002Fhow-to-test-vue-router-components-with-testing-library-and-vitest":204},{"id":4,"title":5,"body":6,"canonical":19,"categories":177,"date":179,"description":180,"draft":181,"extension":182,"image":183,"meta":184,"navigation":188,"path":189,"permalink":183,"readingTime":190,"recommend":183,"references":183,"seo":195,"sitemap":196,"stem":197,"tags":198,"type":183,"updated":202,"__hash__":203},"content\u002Fposts\u002F2025\u002Fhow-to-test-vue-router-components-with-testing-library-and-vitest.md","【翻译】如何使用测试库和 Vitest 测试 Vue Router 组件",{"type":7,"value":8,"toc":161},"minimark",[9,23,26,30,33,37,40,44,48,60,63,66,74,77,80,86,90,93,100,103,109,112,115,121,123,129,132,135,142,145,151,155,158],[10,11,12],"auths-alex",{},[13,14,15,16],"p",{},"原文地址：",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Falexop.dev\u002Fposts\u002Fhow-to-test-vue-router-components-with-testing-library-and-vitest\u002F",[21],"nofollow","How to Test Vue Router Components with Testing Library and Vitest",[24,25],"hr",{},[27,28,29],"h2",{"id":29},"介绍",[13,31,32],{},"现代 Vue 应用程序需要全面测试，以确保可靠的导航和组件性能。我们将介绍使用 测试库和 Vitest 的测试策略，通过路由集成和组件隔离来模拟真实场景。",[27,34,36],{"id":35},"使用测试库和vitest-进行-vue-router-的测试技术","使用测试库和Vitest 进行 Vue Router 的测试技术",[13,38,39],{},"让我们探索如何使用真实路由实例和模拟为 Vue Router 组件编写有效的测试。",[27,41,43],{"id":42},"测试-vue-router-导航组件","测试 Vue Router 导航组件",[45,46,47],"h3",{"id":47},"导航组件示例",[49,50,57],"pre",{"className":51,"code":53,"filename":54,"language":55,"meta":56},[52],"language-vue","\u003Cscript setup lang=\"ts\">\nimport { useRouter } from 'vue-router'\n\nconst router = useRouter()\nfunction goToProfile() {\n    router.push('\u002Fprofile')\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cnav>\n        \u003Crouter-link to=\"\u002Fdashboard\" class=\"nav-link\">\n            Dashboard\n        \u003C\u002Frouter-link>\n        \u003Crouter-link to=\"\u002Fsettings\" class=\"nav-link\">\n            Settings\n        \u003C\u002Frouter-link>\n        \u003Cbutton @click=\"goToProfile\">\n            Profile\n        \u003C\u002Fbutton>\n    \u003C\u002Fnav>\n\u003C\u002Ftemplate>\n","NavigationMenu.vue","vue","",[58,59,53],"code",{"__ignoreMap":56},[45,61,62],{"id":62},"真正的路由集成测试",[13,64,65],{},"使用真实的路由器实例测试完整的路由行为：",[49,67,72],{"className":68,"code":70,"language":71,"meta":56},[69],"language-ts","import { userEvent } from '@testing-library\u002Fuser-event'\nimport { render, screen } from '@testing-library\u002Fvue'\nimport { describe, expect, it } from 'vitest'\nimport { createRouter, createWebHistory } from 'vue-router'\n\nimport NavigationMenu from '..\u002FNavigationMenu..vue'\n\ndescribe('NavigationMenu', () => {\n    it('should navigate using router links', async () => {\n        const router = createRouter({\n            history: createWebHistory(),\n            routes: [\n                { path: '\u002Fdashboard', component: { template: 'Dashboard' } },\n                { path: '\u002Fsettings', component: { template: 'Settings' } },\n                { path: '\u002Fprofile', component: { template: 'Profile' } },\n                { path: '\u002F', component: { template: 'Home' } },\n            ],\n        })\n\n        render(NavigationMenu, {\n            global: {\n                plugins: [router],\n            },\n        })\n\n        const user = userEvent.setup()\n        expect(router.currentRoute.value.path).toBe('\u002F')\n\n        await router.isReady()\n        await user.click(screen.getByText('Dashboard'))\n        expect(router.currentRoute.value.path).toBe('\u002Fdashboard')\n\n        await user.click(screen.getByText('Profile'))\n        expect(router.currentRoute.value.path).toBe('\u002Fprofile')\n    })\n})\n","ts",[58,73,70],{"__ignoreMap":56},[45,75,76],{"id":76},"模拟路由测试",[13,78,79],{},"使用 router mock 隔离测试组件",[49,81,84],{"className":82,"code":83,"language":71,"meta":56},[69],"import type { Router } from 'vue-router';\n\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { render, screen } from '@testing-library\u002Fvue'\nimport { describe, expect, it, vi } from 'vitest'\nimport { useRouter } from 'vue-router'\n\nimport NavigationMenu from '..\u002FNavigationMenu..vue'\n\nconst mockPush = vi.fn()\nvi.mock('vue-router', () => ({\n    useRouter: vi.fn(),\n}))\n\ndescribe('NavigationMenu with mocked router', () => {\n    it('should handle navigation with mocked router', async () => {\n        const mockRouter = {\n            push: mockPush,\n            currentRoute: { value: { path: '\u002F' } },\n        } as unknown as Router\n\n        vi.mocked(useRouter).mockImplementation(() => mockRouter)\n\n        const user = userEvent.setup()\n        render(NavigationMenu)\n\n        await user.click(screen.getByText('Profile'))\n        expect(mockPush).toHaveBeenCalledWith('\u002Fprofile')\n    })\n})\n",[58,85,83],{"__ignoreMap":56},[45,87,89],{"id":88},"用于隔离测试的-routerlink-stub","用于隔离测试的 RouterLink Stub",[13,91,92],{},"创建一个 RouterLink Stub来测试没有 RouterLink 行为的导航",[49,94,98],{"className":95,"code":96,"filename":97,"language":71,"meta":56},[69],"import { Component, h } from 'vue'\nimport { useRouter } from 'vue-router'\n\nexport const RouterLinkStub: Component = {\n    name: 'RouterLinkStub',\n    props: {\n        to: {\n            type: [String, Object],\n            required: true,\n        },\n        tag: {\n            type: String,\n            default: 'a',\n        },\n        exact: Boolean,\n        exactPath: Boolean,\n        append: Boolean,\n        replace: Boolean,\n        activeClass: String,\n        exactActiveClass: String,\n        exactPathActiveClass: String,\n        event: {\n            type: [String, Array],\n            default: 'click',\n        },\n    },\n    setup(props) {\n        const router = useRouter()\n        const navigate = () => {\n            router.push(props.to)\n        }\n        return { navigate }\n    },\n    render() {\n        return h(\n            this.tag,\n            {\n                onClick: () => this.navigate(),\n            },\n            this.$slots.default?.(),\n        )\n    },\n}\n","test-utils.ts",[58,99,96],{"__ignoreMap":56},[13,101,102],{},"在测试中使用 RouterLinkStub：",[49,104,107],{"className":105,"code":106,"language":71,"meta":56},[69],"import type { Router } from 'vue-router';\n\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { render, screen } from '@testing-library\u002Fvue'\nimport { describe, expect, it, vi } from 'vitest'\nimport { useRouter } from 'vue-router'\n\nimport NavigationMenu from '..\u002FNavigationMenu..vue'\nimport { RouterLinkStub } from '.\u002Ftest-utils'\n\nconst mockPush = vi.fn()\nvi.mock('vue-router', () => ({\n    useRouter: vi.fn(),\n}))\n\ndescribe('NavigationMenu with mocked router', () => {\n    it('should handle navigation with mocked router', async () => {\n        const mockRouter = {\n            push: mockPush,\n            currentRoute: { value: { path: '\u002F' } },\n        } as unknown as Router\n\n        vi.mocked(useRouter).mockImplementation(() => mockRouter)\n\n        const user = userEvent.setup()\n        render(NavigationMenu, {\n            global: {\n                stubs: {\n                    RouterLink: RouterLinkStub,\n                },\n            },\n        })\n\n        await user.click(screen.getByText('Dashboard'))\n        expect(mockPush).toHaveBeenCalledWith('\u002Fdashboard')\n    })\n})\n",[58,108,106],{"__ignoreMap":56},[45,110,111],{"id":111},"测试导航守卫",[13,113,114],{},"通过在路由上下文渲染组件来测试导航守卫",[49,116,119],{"className":117,"code":118,"language":55,"meta":56},[52],"\u003Cscript setup lang=\"ts\">\nimport { onBeforeRouteLeave } from 'vue-router'\n\nonBeforeRouteLeave(() => {\n    return window.confirm('Do you really want to leave this page?')\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cdiv>\n        \u003Ch1>Route Leave Guard Demo\u003C\u002Fh1>\n        \u003Cdiv>\n            \u003Cnav>\n                \u003Crouter-link to=\"\u002F\">\n                    Home\n                \u003C\u002Frouter-link> |\n                \u003Crouter-link to=\"\u002Fabout\">\n                    About\n                \u003C\u002Frouter-link> |\n                \u003Crouter-link to=\"\u002Fguard-demo\">\n                    Guard Demo\n                \u003C\u002Frouter-link>\n            \u003C\u002Fnav>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[58,120,118],{"__ignoreMap":56},[13,122,111],{},[49,124,127],{"className":125,"code":126,"language":71,"meta":56},[69],"import userEvent from '@testing-library\u002Fuser-event'\nimport { render, screen } from '@testing-library\u002Fvue'\nimport { beforeEach, describe, expect, it, vi } from 'vitest'\nimport { createRouter, createWebHistory } from 'vue-router'\n\nimport RouteLeaveGuardDemo from '..\u002FRouteLeaveGuardDemo.vue'\n\nconst routes = [\n    { path: '\u002F', component: RouteLeaveGuardDemo },\n    { path: '\u002Fabout', component: { template: '\u003Cdiv>About\u003C\u002Fdiv>' } },\n]\n\nconst router = createRouter({\n    history: createWebHistory(),\n    routes,\n})\n\nconst App = { template: '\u003Crouter-view \u002F>' }\n\ndescribe('RouteLeaveGuardDemo', () => {\n    beforeEach(async () => {\n        vi.clearAllMocks()\n        window.confirm = vi.fn()\n        await router.push('\u002F')\n        await router.isReady()\n    })\n\n    it('should prompt when guard is triggered and user confirms', async () => {\n    \u002F\u002F Set window.confirm to simulate a user confirming the prompt\n        window.confirm = vi.fn(() => true)\n\n        \u002F\u002F Render the component within a router context\n        render(App, {\n            global: {\n                plugins: [router],\n            },\n        })\n\n        const user = userEvent.setup()\n\n        \u002F\u002F Find the 'About' link and simulate a user click\n        const aboutLink = screen.getByRole('link', { name: \u002FAbout\u002Fi })\n        await user.click(aboutLink)\n\n        \u002F\u002F Assert that the confirm dialog was shown with the correct message\n        expect(window.confirm).toHaveBeenCalledWith('Do you really want to leave this page?')\n\n        \u002F\u002F Verify that the navigation was allowed and the route changed to '\u002Fabout'\n        expect(router.currentRoute.value.path).toBe('\u002Fabout')\n    })\n})\n",[58,128,126],{"__ignoreMap":56},[45,130,131],{"id":131},"可复用的路由测试帮助函数",[13,133,134],{},"创建一个辅助函数来简化路由设置",[49,136,140],{"className":137,"code":138,"filename":139,"language":71,"meta":56},[69],"import type { RenderOptions } from '@testing-library\u002Fvue'\n\nimport { render } from '@testing-library\u002Fvue'\nimport { createRouter, createWebHistory } from 'vue-router'\n\n\u002F\u002F path of the definition of your routes\nimport { routes } from '..\u002F..\u002Frouter\u002Findex.ts'\n\ninterface RenderWithRouterOptions extends Omit\u003CRenderOptions\u003Cany>, 'global'> {\n    initialRoute?: string\n    routerOptions?: {\n        routes?: typeof routes\n        history?: ReturnType\u003Ctypeof createWebHistory>\n    }\n}\n\nexport function renderWithRouter(Component: any, options: RenderWithRouterOptions = {}) {\n    const { initialRoute = '\u002F', routerOptions = {}, ...renderOptions } = options\n\n    const router = createRouter({\n        history: createWebHistory(),\n        \u002F\u002F Use provided routes or import from your router file\n        routes: routerOptions.routes || routes,\n    })\n\n    router.push(initialRoute)\n\n    return {\n    \u002F\u002F Return everything from regular render, plus the router instance\n        ...render(Component, {\n            global: {\n                plugins: [router],\n            },\n            ...renderOptions,\n        }),\n        router,\n    }\n}\n","test.utils.ts",[58,141,138],{"__ignoreMap":56},[13,143,144],{},"在测试中使用助手：",[49,146,149],{"className":147,"code":148,"language":71,"meta":56},[69],"describe('NavigationMenu', () => {\n    it('should navigate using router links', async () => {\n        const { router } = renderWithRouter(NavigationMenu, {\n            initialRoute: '\u002F',\n        })\n\n        await router.isReady()\n        const user = userEvent.setup()\n\n        await user.click(screen.getByText('Dashboard'))\n        expect(router.currentRoute.value.path).toBe('\u002Fdashboard')\n    })\n})\n",[58,150,148],{"__ignoreMap":56},[27,152,154],{"id":153},"结论vue-router-组件测试的最佳实践","结论：Vue Router 组件测试的最佳实践",[13,156,157],{},"当我们测试依赖于路由的组件时，我们需要考虑是否要在最真实的用例中测试功能，还是单独测试。在我看来，模拟测试越多，测试结果就越糟糕。我个人的建议是尽量使用真正的路由，而不是模拟它。有时，也会有例外，所以请记住这一点。",[13,159,160],{},"此外，您可以通过专注于不依赖路由功能的组件来帮助自己。为视图\u002F页面组件保留路由逻辑。在保持组件简单的同时，我们永远不会遇到模拟路由的问题。",{"title":56,"searchDepth":162,"depth":162,"links":163},4,[164,166,167,176],{"id":29,"depth":165,"text":29},2,{"id":35,"depth":165,"text":36},{"id":42,"depth":165,"text":43,"children":168},[169,171,172,173,174,175],{"id":47,"depth":170,"text":47},3,{"id":62,"depth":170,"text":62},{"id":76,"depth":170,"text":76},{"id":88,"depth":170,"text":89},{"id":111,"depth":170,"text":111},{"id":131,"depth":170,"text":131},{"id":153,"depth":165,"text":154},[178],"译","2025-03-02 09:45:11","本指南将向你展示如何使用真实的路由集成来测试 Vue Router 组件，并使用 mock 进行隔离组件测试。您将学习验证路由-链路交互、编程导航和导航守卫处理。",false,"md",null,{"aside":185},[186,187],"toc","auth-alex",true,"\u002Fposts\u002F2025\u002Fhow-to-test-vue-router-components-with-testing-library-and-vitest",{"text":191,"minutes":192,"time":193,"words":194},"7 min read",6.5,390000,1300,{"title":5,"description":180},{"loc":189},"posts\u002F2025\u002Fhow-to-test-vue-router-components-with-testing-library-and-vitest",[199,200,201],"Vitest","Vue Router","Vue","2026-02-07 23:23:50","Ss6HHq9MUOMswuzujlHo45-Oa879aTdF6I6vQ86Q5kI",[205,211],{"title":206,"path":207,"stem":208,"date":209,"type":210,"children":-1},"编程学习网站","\u002Fposts\u002F2025\u002Flearn-site","posts\u002F2025\u002Flearn-site","2025-02-24 15:43:24","story",{"title":212,"path":213,"stem":214,"date":215,"type":183,"children":-1},"【翻译】如何使用 Vitest 在 Vue 中进行视觉回归测试？","\u002Fposts\u002F2025\u002Fhow-to-do-visual-regression-testing-in-vue-with-vitest","posts\u002F2025\u002Fhow-to-do-visual-regression-testing-in-vue-with-vitest","2025-03-02 10:10:47",1789642590006]