news 2026/5/1 8:17:04

前端——单元测试实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端——单元测试实践

背景问题:
需要为 Vue3 + Vite 项目编写单元测试。

方案思考:
使用 Vitest 作为测试框架,结合 @vue/test-utils 进行组件测试。

具体实现:
安装测试依赖:

# 安装 Vitest 和 Vue 测试工具npminstall-D vitest @vue/test-utils jsdom happy-dom# 安装断言库(Vitest 默认包含 Chai 断言)npminstall-D @vitest/coverage-v8

Vitest 配置:

// vite.config.jsimport{defineConfig}from'vite'importvuefrom'@vitejs/plugin-vue'exportdefaultdefineConfig({plugins:[vue()],test:{// 启用 Vitest 的 APIglobals:true,// 指定测试环境environment:'jsdom',// 或 'happy-dom'// 包含测试文件的模式include:['tests/**/*.test.{js,ts}'],// 排除文件的模式exclude:['node_modules','dist','.idea','.git','.cache'],// 测试覆盖率配置coverage:{provider:'v8',// 或 'istanbul'reporter:['text','json','html'],reportsDirectory:'./coverage',exclude:['node_modules/**','tests/**','vite.config.js']},// 模拟 DOM 环境setupFiles:['./tests/setup.js']}})

测试工具配置:

// tests/setup.jsimport{expect,afterEach}from'vitest'import{cleanup}from'@testing-library/vue'importmatchersfrom'@vitest/expect'// 扩展 Vitest 的 expectexpect.extend(matchers)// 在每个测试后清理 DOMafterEach(()=>{cleanup()})

组件测试示例:

<!-- components/HelloWorld.vue --> <template> <h1>{{ msg }}</h1> <button @click="count++">count is: {{ count }}</button> <p>Edit <code>components/HelloWorld.vue</code> to test hot module replacement.</p> </template> <script setup> import { ref } from 'vue' defineProps({ msg: String }) const count = ref(0) </script>
// tests/unit/HelloWorld.test.jsimport{describe,it,expect}from'vitest'import{mount}from'@vue/test-utils'importHelloWorldfrom'@/components/HelloWorld.vue'describe('HelloWorld',()=>{it('renders properly',()=>{constwrapper=mount(HelloWorld,{props:{msg:'Hello World'}})expect(wrapper.text()).toContain('Hello World')})it('counter starts at 0 and increments',async()=>{constwrapper=mount(HelloWorld)// 初始值expect(wrapper.find('button').text()).toBe('count is: 0')// 点击按钮awaitwrapper.find('button').trigger('click')// 验证值已更新expect(wrapper.find('button').text()).toBe('count is: 1')})it('reactive props work correctly',()=>{constwrapper=mount(HelloWorld,{props:{msg:'Updated Message'}})expect(wrapper.find('h1').text()).toBe('Updated Message')})})

组合式函数测试:

// composables/useCounter.jsimport{ref}from'vue'exportfunctionuseCounter(initialValue=0){constcount=ref(initialValue)constincrement=()=>{count.value++}constdecrement=()=>{count.value--}constreset=()=>{count.value=initialValue}return{count,increment,decrement,reset}}
// tests/unit/composables/useCounter.test.jsimport{describe,it,expect}from'vitest'import{useCounter}from'@/composables/useCounter'describe('useCounter',()=>{it('should initialize with default value 0',()=>{const{count}=useCounter()expect(count.value).toBe(0)})it('should initialize with provided initial value',()=>{const{count}=useCounter(5)expect(count.value).toBe(5)})it('should increment counter',()=>{const{count,increment}=useCounter(0)increment()expect(count.value).toBe(1)})it('should decrement counter',()=>{const{count,decrement}=useCounter(5)decrement()expect(count.value).toBe(4)})it('should reset counter to initial value',()=>{const{count,increment,reset}=useCounter(10)increment()increment()expect(count.value).toBe(12)reset()expect(count.value).toBe(10)})})

API 测试:

// utils/request.js (测试版)importaxiosfrom'axios'// Mock axiosexportconstmockAxios={get:vi.fn(),post:vi.fn(),put:vi.fn(),delete:vi.fn()}exportdefault{get:(url,config)=>mockAxios.get(url,config),post:(url,data,config)=>mockAxios.post(url,data,config),put:(url,data,config)=>mockAxios.put(url,data,config),delete:(url,config)=>mockAxios.delete(url,config)}
// tests/unit/utils/api.test.jsimport{describe,it,expect,beforeEach,vi}from'vitest'import{mockAxios}from'@/utils/request'describe('API utilities',()=>{beforeEach(()=>{// 重置 mockvi.clearAllMocks()})it('should make GET request',async()=>{constmockResponse={data:{id:1,name:'Test'}}mockAxios.get.mockResolvedValue(mockResponse)constresult=awaitmockAxios.get('/api/users/1')expect(mockAxios.get).toHaveBeenCalledWith('/api/users/1')expect(result).toEqual(mockResponse)})it('should handle API errors',async()=>{constmockError=newError('Network Error')mockAxios.get.mockRejectedValue(mockError)awaitexpect(mockAxios.get('/api/users/1')).rejects.toThrow('Network Error')expect(mockAxios.get).toHaveBeenCalledWith('/api/users/1')})})

测试工具函数:

// tests/utils/index.jsimport{mount}from'@vue/test-utils'import{setActivePinia,createPinia}from'pinia'// 创建带 Pinia 的包装器exportfunctioncreateWrapper(Component,options={}){// 创建新的 Pinia 实例constpinia=createPinia()returnmount(Component,{...options,global:{plugins:[pinia],...(options.global||{})}})}// 模拟路由exportfunctionmockRouter(){return{push:vi.fn(),replace:vi.fn(),go:vi.fn(),back:vi.fn(),forward:vi.fn()}}// 模拟响应式数据exportfunctionmockReactive(obj){returnvi.fn(()=>obj)}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 15:10:51

Vue 3 中的具名插槽仍然完全支持,Vue 2 的旧语法 Vue 3 中已废弃

Vue3中具名插槽的使用方式更加统一和简洁。新版本采用v-slot指令&#xff08;简写为#&#xff09;替代Vue2的slot和slot-scope属性&#xff0c;支持默认插槽、具名插槽和作用域插槽。子组件通过name属性定义插槽&#xff0c;父组件使用#插槽名语法插入内容。Vue3还增强了动态插…

作者头像 李华
网站建设 2026/4/30 12:47:43

Roots.ai团队推出GutenOCR:让AI既能读字又能精准定位

这项由Roots.ai团队开展的研究发表于2026年1月的arXiv预印本服务器&#xff0c;论文编号为arXiv:2601.14490v1。有兴趣深入了解技术细节的读者可以通过该编号查询完整论文。 当你用手机扫描一份文件时&#xff0c;是否曾经遇到过这样的困扰&#xff1a;软件能够识别出文字内容&…

作者头像 李华
网站建设 2026/5/1 10:59:31

Parameter Lab团队揭示:微调大模型如何意外引发“隐私坍塌“

当我们把大型语言模型比作一个刚入职的新员工时&#xff0c;微调就像是对其进行岗位培训。原本我们认为这种培训只会让AI变得更专业、更有用&#xff0c;就像教会新员工使用公司特定的软件或遵循特定的工作流程一样。然而&#xff0c;Parameter Lab、图宾根大学、达姆施塔特工业…

作者头像 李华
网站建设 2026/5/1 4:39:49

低分辨率图像目标检测性能验证:YOLOv8集成SPD-Conv的实战评测与优化

文章目录 SPD-Conv:革新低分辨率图像和小目标检测的新型CNN构建块完整教程 引言 1. SPD-Conv 技术背景与核心原理 1.1 传统CNN架构的局限性 1.2 SPD-Conv的设计理念 1.3 SPD-Conv的工作机制详解 步骤1:空间到深度(Space-to-Depth)转换 步骤2:非步长卷积处理 2. 环境配置与…

作者头像 李华
网站建设 2026/5/1 10:18:01

构建行星级AI系统:机器学习决策前沿

构建行星级AI系统&#xff1a;机器学习决策前沿 四月底&#xff0c;全球人工智能科学家齐聚线上&#xff0c;参加国际学习表征会议&#xff08;ICLR&#xff09;。该会议聚焦于人工智能、统计学和数据科学的进步&#xff0c;以及计算机视觉、计算生物学、语音识别、文本理解、游…

作者头像 李华
网站建设 2026/5/1 6:29:38

系统存储机制深度剖析:从Win11临时文件夹设计看微软存储架构演进

临时文件夹作为Windows系统的“临时工作台”&#xff0c;承载着程序运行缓存、安装包残留、网页临时文件等核心任务&#xff0c;其设计逻辑直接映射着微软存储架构的迭代思路。从Win95的分散存储到Win11的智能管控&#xff0c;临时文件夹的每一次优化&#xff0c;都是微软对“高…

作者头像 李华