ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

claude-skills 实战:用 Vitest 与 Vue Test Utils 为 Vue 3 组件构建完整测试体系(vue-expert-js 测试模式指南)

claude-skills 实战:用 Vitest 与 Vue Test Utils 为 Vue 3 组件构建完整测试体系(vue-expert-js 测试模式指南) claude-skills 实战用 Vitest 与 Vue Test Utils 为 Vue 3 组件构建完整测试体系vue-expert-js 测试模式指南【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills导读本指南以 claude-skills 仓库中 vue-expert-js 技能的 testing-patterns 参考文档 为核心骨架面向使用纯 JavaScript无 TypeScript构建 Vue 3 应用的开发者系统讲解基于 Vitest 与 Vue Test Utils 的组件测试完整方案。读完本文你将掌握测试环境初始化、组件渲染与事件断言、v-model 双向绑定验证、异步请求处理、Composable 与 Pinia 状态 Mock、provide/inject 依赖注入测试等全套实战技能并了解这些模式在 claude-skills 技能库中的定位与搭配方式。该技能是 claude-skills 仓库 67 个全栈开发技能之一定位为纯 JavaScript JSDoc 类型标注的 Vue 专家其 SKILL.md 明确要求使用 Composition API 的script setup语法不带langts禁止.ts扩展名用typedef、param、returns等 JSDoc 注释实现完整的类型覆盖。在这种JS 优先的技术栈下测试环节同样以纯 JS 的 Vitest 测试文件为主。一、测试环境搭建vitest.config.js 与全局 Setup1.1 Vitest 配置jsdom 环境与全局 API组件测试需要一个模拟浏览器 DOM 的运行环境。Vue 官方测试工具链中Vitest 负责运行与断言Vue Test Utils 负责挂载组件jsdom 负责提供 DOM API。以下是该技能推荐的 vitest.config.js 配置// vitest.config.js import { defineConfig } from vitest/config import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], test: { environment: jsdom, globals: true, setupFiles: [./vitest.setup.js] } })各配置项说明配置项作用说明plugins: [vue()]集成vitejs/plugin-vue让 Vitest 复用 Vite 对.vue单文件组件的编译能力这是组件测试的前提environment: jsdom指定测试环境jsdom 在 Node 中模拟浏览器 DOM若测试不涉及 DOM 可改用node以提升速度globals: true启用全局 API让describe、it、expect等无需 import 即可使用测试代码更简洁setupFiles: [./vitest.setup.js]测试启动前执行的准备文件集中放置全局 Mock 与通用配置注即使启用了globals原文档的测试示例仍显式 import 了describe, it, expect这在团队代码中更利于 IDE 类型提示配合该技能的 JSDoc 类型约定两种写法均可行。1.2 全局 SetupMock fetch 与通用组件 Stubvitest.setup.js 负责在测试启动时注册全局 Mock// vitest.setup.js import { config } from vue/test-utils import { vi } from vitest vi.stubGlobal(fetch, vi.fn()) config.global.stubs { router-link: { template: aslot //a } }逐行拆解vi.stubGlobal(fetch, vi.fn())把全局fetch替换为可编程的 Mock 函数。Vue 3 应用中大量组件依赖fetch发起请求参考 state-management.md 中loginaction 对/api/login的调用在 jsdom 环境中该 API 默认不可用统一打桩后即可在单个测试中按需 mock 返回值。config.global.stubsVue Test Utils 的全局配置。凡是模板里出现router-link的地方一律替换为一个渲染aslot //a的轻量桩组件。这样被测组件不再依赖 Vue Router 实例渲染出的锚点文本仍可被wrapper.text()断言。同理可扩展 stubtransition、teleport等内置组件。这套环境全局打桩 组件全局 stub的写法让每个测试文件只需关注自身逻辑无需重复处理基础设施。二、组件测试基础渲染、插槽、类名、事件与属性组件测试的核心循环是挂载 → 交互 → 断言。原文档以 Button 组件为例覆盖了 Vue 组件四大基础断言面// Button.test.js import { describe, it, expect } from vitest import { mount } from vue/test-utils import Button from ./Button.vue describe(Button, () { it(renders slot content, () { const wrapper mount(Button, { slots: { default: Click me } }) expect(wrapper.text()).toBe(Click me) }) it(applies variant class, () { const wrapper mount(Button, { props: { variant: danger } }) expect(wrapper.classes()).toContain(btn--danger) }) it(emits click event, async () { const wrapper mount(Button) await wrapper.trigger(click) expect(wrapper.emitted(click)).toHaveLength(1) }) it(is disabled when prop is true, () { const wrapper mount(Button, { props: { disabled: true } }) expect(wrapper.attributes(disabled)).toBeDefined() }) })四个断言技巧逐一说明插槽渲染mount(Button, { slots: { default: Click me } })注入默认插槽内容wrapper.text()返回组件渲染的完整文本。断言插槽是否被正确渲染与透传。props 驱动的类名传入variant: danger断言组件根据 variant 动态拼接的 CSS 类。若 Button 内部按:class\btn--${variant} 生成类名测试即验证了输入 prop → 输出样式的契约。事件触发trigger(click)模拟点击wrapper.emitted(click)返回该事件所有发射记录的数组。toHaveLength(1)验证事件恰好触发一次。注意触发事件通常需要await等待 DOM 更新与事件队列刷新。属性断言wrapper.attributes(disabled)检查渲染出的 HTML 是否带disabled属性。这段示例与 SKILL.md 中 JSDoc 化 props 定义形成闭环技能要求组件用typedefdefineProps声明 props如isAdmin布尔默认值、variant校验器等而测试正是对这些声明契约的逐项验证。三、v-model 双向绑定测试验证 update:modelValue 事件v-model 本质上是modelValueprop 与update:modelValue事件的语法糖。测试 v-model 的正确姿势是模拟用户输入断言update:modelValue事件携带了正确的载荷。原文档的 TextInput 示例// TextInput.test.js import { describe, it, expect } from vitest import { mount } from vue/test-utils import TextInput from ./TextInput.vue describe(TextInput, () { it(emits update:modelValue on input, async () { const wrapper mount(TextInput, { props: { modelValue: } }) await wrapper.find(input).setValue(new value) expect(wrapper.emitted(update:modelValue)).toEqual([[new value]]) }) })要点setValue(new value)会设置 input 的 value 并触发input事件是模拟用户输入的标准方式也可用于select、textarea。emitted(update:modelValue)返回二维数组外层每项对应一次事件发射内层是该次发射的载荷列表因此toEqual([[new value]])断言恰好发射一次载荷为字符串new value。组件侧的实现可参考 component-architecture.md 中的 v-model 模式defineProps({ modelValue })defineEmits([update:modelValue])模板中:valuemodelValue inputemit(update:modelValue, $event.target.value)。该文档还给出了多 v-modelv-model:firstName→update:firstName的扩展写法对应测试只需断言emitted(update:firstName)即可。四、异步测试flushPromises 与 fetch Mock 组合Vue 组件的onMounted中常发异步请求。测试这类组件必须等待 Promise 队列清空否则断言发生在渲染之前。Vue Test Utils 提供了flushPromises()工具原文档的 UserList 示例是标准范式// UserList.test.js import { describe, it, expect, vi, beforeEach } from vitest import { mount, flushPromises } from vue/test-utils import UserList from ./UserList.vue describe(UserList, () { beforeEach(() vi.resetAllMocks()) it(renders users after fetch, async () { global.fetch vi.fn().mockResolvedValue({ ok: true, json: () Promise.resolve([{ id: 1, name: Alice }]) }) const wrapper mount(UserList) await flushPromises() expect(wrapper.text()).toContain(Alice) }) it(shows error on failure, async () { global.fetch vi.fn().mockRejectedValue(new Error(Network error)) const wrapper mount(UserList) await flushPromises() expect(wrapper.find([data-testerror]).exists()).toBe(true) }) })要点拆解beforeEach(() vi.resetAllMocks())每个测试前重置所有 mock包括 setup 文件里全局打桩的fetch避免测试间状态泄漏。因为 setup 中已用vi.stubGlobal(fetch, vi.fn())打桩此处直接改写global.fetch的 mock 实现即可。成功路径mockResolvedValue返回一个模拟 Response 对象其json()也返回 Promise构造完整的链式调用flushPromises()等待 fetch 的 resolve 与组件内部的渲染队列全部完成然后断言文本包含 Alice。失败路径mockRejectedValue(new Error(...))模拟网络故障断言组件渲染了data-testerror的错误提示元素。这里体现了以data-test属性作为查询锚点的约定——比依赖 CSS 类名更稳定与原文档 Quick Reference 中wrapper.find([data-testx])的推荐一致。从底层原理看flushPromises()本质是清空当前微任务队列Vue 组件的异步渲染如nextTick回调、Promise 链与watchEffect的触发都依赖微任务调度await flushPromises()保证这些任务全部执行完毕。五、Mocking Composablesvi.spyOn 替换组合式函数Composable 是 Vue 3 Composition API 的逻辑复用单元也是 JS 版 Vue 项目的核心组织方式参考 composables-patterns.md其中useToggle、useAsyncState、useCancellableFetch等均以返回{ ref, 方法 }对象为统一结构。测试依赖 Composable 的组件时若不想真实执行内部副作用如登录请求、定时器可用vi.spyOn整体替换。原文档 Header 示例// Header.test.js import { describe, it, expect, vi } from vitest import { mount } from vue/test-utils import { ref, computed } from vue import Header from ./Header.vue import * as useAuthModule from /composables/useAuth describe(Header, () { it(shows login button when logged out, () { vi.spyOn(useAuthModule, useAuth).mockReturnValue({ user: ref(null), isLoggedIn: computed(() false), login: vi.fn(), logout: vi.fn() }) const wrapper mount(Header) expect(wrapper.find([data-testlogin-btn]).exists()).toBe(true) }) it(shows user menu when logged in, () { vi.spyOn(useAuthModule, useAuth).mockReturnValue({ user: ref({ id: 1, name: John }), isLoggedIn: computed(() true), login: vi.fn(), logout: vi.fn() }) const wrapper mount(Header) expect(wrapper.find([data-testuser-menu]).exists()).toBe(true) }) })关键点import * as useAuthModule from /composables/useAuth把模块整体导入为命名空间对象才能对其属性进行vi.spyOnESM 命名导出的标准 Mock 方式。Mock 返回值必须保持响应式形状user必须是ref()包装的响应式值isLoggedIn必须是computed()。因为组件模板在渲染时会解包这些 ref/computed如果直接返回普通对象组件内部读取.value将得到undefined测试就会失真。这也印证了该技能Composable 返回{ ref, computed, 函数 }结构的约定——Mock 只是按相同形状伪造数据。行为分支全覆盖分别 mock 为未登录与已登录两种状态断言[data-testlogin-btn]与[data-testuser-menu]的条件渲染验证组件的状态切换逻辑。六、Pinia 状态测试createTestingPinia 提供隔离 Store现代 Vue 3 应用的状态管理以 Pinia 为主参考 state-management.md其中同时演示了 Options 语法与 Setup 语法定义 store、storeToRefs解构、store 间组合调用等模式。测试依赖 store 的组件时pinia/testing提供的createTestingPinia能创建默认所有 action 均为 vi.fn()、state 可用 initialState 预设的隔离实例。原文档 CartSummary 示例// CartSummary.test.js import { describe, it, expect } from vitest import { mount } from vue/test-utils import { createTestingPinia } from pinia/testing import CartSummary from ./CartSummary.vue import { useCartStore } from /stores/cart describe(CartSummary, () { it(displays cart total, () { const wrapper mount(CartSummary, { global: { plugins: [createTestingPinia({ initialState: { cart: { items: [{ productId: 1, quantity: 2, price: 100 }] } } })] } }) expect(wrapper.text()).toContain($200) }) it(calls checkout action, async () { const wrapper mount(CartSummary, { global: { plugins: [createTestingPinia()] } }) await wrapper.find([data-testcheckout-btn]).trigger(click) expect(useCartStore().checkout).toHaveBeenCalled() }) })要点注入方式Pinia 插件通过global.plugins数组传入组件内部的useCartStore()会自动找到这个测试实例无需手动setActivePinia。initialState预设 state键为 store idcart值为该 store 的 state 快照。组件渲染时从 store 读取items计算总额2 × $100 $200断言文本。action 自动 MockcreateTestingPinia()默认把所有 actions 替换为vi.fn()因此useCartStore().checkout在测试中不会真正发起网络请求只需断言点击结算按钮后该 action 被调用。这正是上一节 state-management 文档中checkout()调用/api/checkout的实现逻辑——测试时用测试 Pinia 隔离掉真实副作用。配合 store 单元测试如果想单独测试 store 本身不经组件state-management.md 给出了另一套范式beforeEach(() setActivePinia(createPinia()))后用useCounterStore()直接断言increment()与 getter 结果。两者组合即可覆盖store 逻辑层 组件渲染层。七、provide/inject 测试global.provide 注入依赖组件间依赖注入provide/inject用于跨层级传递数据。测试注入方组件时Vue Test Utils 允许在挂载配置中直接通过global.provide提供注入值无需真正渲染 Provider 祖先。原文档示例// ChildComponent.test.js import { describe, it, expect } from vitest import { mount } from vue/test-utils import { ref } from vue import ChildComponent from ./ChildComponent.vue describe(ChildComponent, () { it(uses injected theme, () { const wrapper mount(ChildComponent, { global: { provide: { theme: ref(dark) } } }) expect(wrapper.classes()).toContain(theme-dark) }) })global.provide的对象键可以是字符串与组件内inject(theme, ...)对应也可以是 Symbol。注入值同样需要保持响应式形状这里theme用ref(dark)包装与组件内const theme inject(theme)后在模板中解包使用的方式匹配。该模式与 component-architecture.md 中provideTheme/useTheme的 Symbol 组合式模式互补若组件通过useTheme()消费注入测试需用相同的ThemeSymbol作为provide的键若注入缺失useTheme()会抛出useTheme requires ThemeProvider错误测试正好可以覆盖未包裹 Provider 时给出明确报错的边界场景。八、测试骨架速查表原文档末尾提供了一张高频操作速查表覆盖组件测试最常用的 API 组合在此完整保留并补充说明任务代码挂载组件mount(Component, { props, slots, global })查找元素wrapper.find([data-testx])触发事件await wrapper.trigger(click)检查事件wrapper.emitted(event)设置输入await wrapper.find(input).setValue(x)等待异步await flushPromises()Mock Composablevi.spyOn(module, fn).mockReturnValue()Mock fetchglobal.fetch vi.fn().mockResolvedValue()测试 PiniacreateTestingPinia({ initialState })注入依赖global: { provide: { key: value } }桩替换组件global: { stubs: { Comp: true } }补充使用语境flushPromises仅在组件内部存在异步逻辑fetch、定时器、nextTick后的异步渲染时必须使用纯同步组件可直接断言。stubs: { Comp: true }true表示用空组件替换也可传入{ template: ... }提供定制桩模板与 setup 文件里对router-link的全局 stub 思路一致。global配置是贯穿全篇的关键挂载选项props/slots/global三者为mount最常用的三个配置维度。九、在 claude-skills 技能体系中的定位与工作流衔接本测试模式不是孤立的知识点而是 vue-expert-js 技能完整工作流的最后一环。查看该技能 SKILL.md 的 Core WorkflowDesign architecture规划组件结构与带 JSDoc 类型标注的 ComposableImplement用script setup不带langts与.mjs模块实现Annotate为所有公共 API 补齐typedef、param、returns并用 ESLint 的 JSDoc 插件校验覆盖度Test使用 JavaScript 文件通过 Vitest 验证确认所有公共 API 的 JSDoc 覆盖测试失败则回到对应组件或 Composable 修正逻辑或注解直到测试套件全绿。这意味着测试文件与源码同为纯 JS不引入.ts测试文件遵守技能 MUST NOT DO 中不使用 TypeScript 语法的约束类型与测试互为佐证JSDoc 声明的 props 形状、Composable 返回结构、store state 结构正是测试中 mock 数据与断言的对象参考文档按需加载SKILL.md 的 Reference Guide 表格将本测试模式文档标记为Testing 主题Vitest、组件测试、Mocking时加载与 jsdoc-typing.md、composables-patterns.md、component-architecture.md、state-management.md 并列。此外claude-skills 仓库还通过 scripts/validate-skills.py 对每个技能的 SKILL.md 进行结构校验包括 Core Workflow 步骤数、引用文件路径可解析性、YAML frontmatter 完整性等而 Makefile 中的validate目标会串联运行这些校验脚本。技能文档体系的健康度本身就有自动化保障这间接保证了本文引用的模式与技能约定始终保持一致。十、常见误区与最佳实践小结避免的典型错误忽略awaittrigger、setValue、flushPromises返回 Promise不await会导致断言过早执行得到空 emitted 或未渲染的 DOM。Mock 形状失真mock 返回的 ref/computed 必须是真实的响应式包装否则组件内.value解包失败同理 store 的 initialState 键名必须与 store id 严格一致。忘记隔离vi.resetAllMocks()或vi.clearAllMocks()未在beforeEach调用时前一个用例的 fetch 实现会泄漏到后一个用例产生偶发失败。查询锚点脆弱优先使用data-test属性定位元素而非依赖易变的 CSS 类名或深层 DOM 结构。推荐实践三层测试分层Composable直接调用 断言返回值→ StoresetActivePinia(createPinia())单元测试→ 组件mount 交互 emitted/文本断言三层各有独立范式按需取舍而非全部照搬。全局 stub 集中管理router-link、transition等通用桩放在vitest.setup.js的config.global.stubs中单测中只为特殊场景定制桩。始终围绕组件契约写断言props 传入 → 渲染结果、事件触发 → emitted 载荷、注入依赖 → 派生样式/行为测试即是对 JSDoc 声明的可执行验证。延伸阅读testing-patterns.md本文核心文档原文component-architecture.mdprops/emits/v-model/provide-inject 组件契约的完整定义state-management.mdPinia store 定义与 store 单元测试范式composables-patterns.mdComposable 结构约定Mock 时需保持相同返回形状jsdoc-typing.mdtypedef/param/returns完整类型标注规则SKILL.mdvue-expert-js 技能总览与 Core Workflow【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表