18-Vue2 单元测试与 E2E 测试

发布时间:2026/6/27 10:58:59
18-Vue2 单元测试与 E2E 测试 Vue2 单元测试与 E2E 测试测试是保证代码质量的关键环节。本章将系统讲解 Vue2 项目的测试体系包括单元测试Jest/Vue Test Utils和端到端测试Cypress帮助你建立可靠的测试防护网。一、前言前端测试金字塔单元测试 70%集成测试 20%E2E测试 10%组件逻辑工具函数Vuex/Mutations组件交互API接口用户流程跨页面操作二、单元测试基础2.1 技术选型工具用途Jest测试框架 断言库 MockVue Test UtilsVue 组件测试工具vue/cli-plugin-unit-jestVue CLI 集成vueaddvue/unit-jest2.2 第一个测试// src/utils/sum.jsexportfunctionsum(a,b){returnab;}// tests/unit/sum.spec.jsimport{sum}from/utils/sum;describe(sum,(){it(adds 1 2 to equal 3,(){expect(sum(1,2)).toBe(3);});it(adds negative numbers,(){expect(sum(-1,-2)).toBe(-3);});});三、组件测试3.1 渲染组件import{shallowMount,mount}fromvue/test-utils;importHelloWorldfrom/components/HelloWorld.vue;describe(HelloWorld.vue,(){// shallowMount - 只渲染当前组件子组件用 stub 替代it(renders props.msg,(){constmsgHello;constwrappershallowMount(HelloWorld,{propsData:{msg}});expect(wrapper.text()).toMatch(msg);});// mount - 完全渲染包括子组件it(mount renders deeply,(){constwrappermount(HelloWorld);expect(wrapper.find(.hello).exists()).toBe(true);});});3.2 常用断言// 查找元素wrapper.find(.class);// 查找第一个匹配wrapper.findAll(li);// 查找所有匹配wrapper.findComponent(Child);// 查找组件// 存在性expect(wrapper.find(.btn).exists()).toBe(true);// 文本内容expect(wrapper.text()).toContain(标题);expect(wrapper.find(h1).text()).toBe(标题);// 属性expect(wrapper.find(img).attributes(src)).toBe(/logo.png);// 类名expect(wrapper.classes()).toContain(active);expect(wrapper.find(.item).classes(selected)).toBe(true);// 样式expect(wrapper.find(.box).element.style.color).toBe(red);3.3 交互测试describe(Counter.vue,(){it(increments count when button is clicked,async(){constwrappershallowMount(Counter);// 初始状态expect(wrapper.find(.count).text()).toBe(0);// 触发点击awaitwrapper.find(button).trigger(click);// 验证更新expect(wrapper.find(.count).text()).toBe(1);});it(emits event on submit,async(){constwrappershallowMount(Form);awaitwrapper.find(input).setValue(test);awaitwrapper.find(form).trigger(submit);expect(wrapper.emitted().submit).toBeTruthy();expect(wrapper.emitted().submit[0]).toEqual([test]);});});四、Vuex 测试4.1 测试 Mutations// store/modules/counter.jsconstmutations{INCREMENT(state){state.count;},SET_COUNT(state,count){state.countcount;}};// tests/unit/store/counter.spec.jsimportmutationsfrom/store/modules/counter;describe(counter mutations,(){it(INCREMENT,(){conststate{count:0};mutations.INCREMENT(state);expect(state.count).toBe(1);});it(SET_COUNT,(){conststate{count:0};mutations.SET_COUNT(state,10);expect(state.count).toBe(10);});});4.2 测试 Actionsimportactionsfrom/store/modules/user;constcommitjest.fn();describe(user actions,(){it(login commits SET_TOKEN on success,async(){constmockResponse{data:{token:abc123}};jest.spyOn(api,login).mockResolvedValue(mockResponse);awaitactions.login({commit},{username:test,password:123});expect(commit).toHaveBeenCalledWith(SET_TOKEN,abc123);});it(login throws error on failure,async(){jest.spyOn(api,login).mockRejectedValue(newError(fail));awaitexpect(actions.login({commit},{})).rejects.toThrow(fail);});});五、Mock 与 Stub5.1 Mock 模块// Mock axiosjest.mock(axios,()({get:jest.fn(()Promise.resolve({data:[]})),post:jest.fn()}));// Mock 组件jest.mock(/components/Chart,()({name:Chart,render:hh(div)}));5.2 Stub 子组件constwrappershallowMount(Parent,{stubs:{// 简单 stubel-button:true,// 自定义 stubrouter-link:{template:aslot //a}}});六、E2E 测试Cypress6.1 安装配置vueadde2e-cypress// cypress.json{baseUrl:http://localhost:8080,viewportWidth:1280,viewportHeight:720}6.2 编写 E2E 测试// tests/e2e/specs/login.cy.jsdescribe(Login,(){beforeEach((){cy.visit(/login);});it(shows error with invalid credentials,(){cy.get([data-testusername]).type(admin);cy.get([data-testpassword]).type(wrong);cy.get([data-testsubmit]).click();cy.get(.el-message--error).should(be.visible);cy.url().should(include,/login);});it(redirects to dashboard on success,(){cy.get([data-testusername]).type(admin);cy.get([data-testpassword]).type(correct);cy.get([data-testsubmit]).click();cy.url().should(include,/dashboard);cy.get(.dashboard).should(exist);});});6.3 Cypress 常用命令// 查询cy.get(.button);cy.find([data-testiditem]);cy.contains(Submit);// 交互cy.click();cy.type(hello);cy.clear();cy.select(option);// 断言cy.should(be.visible);cy.should(have.class,active);cy.should(contain,text);cy.url().should(include,/path);// 等待cy.wait(1000);cy.wait(apiRequest);// 自定义命令cy.login(admin,password);七、测试覆盖率# 生成覆盖率报告npmrun test:unit ----coverage# 配置 jest.config.jsmodule.exports{collectCoverage: true, collectCoverageFrom:[src/**/*.{js,vue},!src/main.js], coverageThreshold:{global:{branches:80, functions:80, lines:80, statements:80}}};八、总结测试类型工具适用场景运行速度单元测试Jest Vue Test Utils组件/函数逻辑快集成测试Jest多组件交互中E2E测试Cypress完整用户流程慢测试原则测试行为而非实现一个测试只验证一个概念使用data-test属性选择元素保持测试独立不依赖执行顺序九、练习为一个计数器组件编写完整的单元测试测试一个表单组件的验证逻辑使用 Cypress 编写登录到下单的完整 E2E 流程配置测试覆盖率确保核心模块达到 80%