详解使用jest对vue项目进行单元测试

作者:脚本小铺

时间:2026-10-22

浏览:489

本文详细记录了在Vue CLI 3.0环境下使用Jest进行单元测试的全过程。从基础配置入手,解决了静态图片资源在测试中返回对象而非字符串的问题,并通过jest-transform-stub和fileMock.js实现正确Mock。进一步演示了使用Vue Test Utils进行组件交互测试、快照测试,以及如何通过Manual Mocks模拟Axios插件和Store状态,最终生成完整的代码覆盖率报告。

文章目录

Vue项目单元测试背景与准备

近期,团队对前端开发提出了引入单元测试的新要求。为了实践这一需求,我选择了之前使用Vue开发的“快报名”小程序PC端页面作为测试对象。虽然此前有过React项目的测试经验,但这是首次尝试在Vue项目中开展单元测试工作。

鉴于Vue CLI 3.0在单元测试支持上更为完善,我将项目升级至该版本。由于项目基于TypeScript开发,需要引入ts-jest配合使用,最终确定的Jest配置如下所示。

Jest基础配置与静态资源处理


{
 "jest": {
  "moduleFileExtensions": [
   "js",
   "jsx",
   "json",
   "vue",
   "ts",
   "tsx"
  ],
  "transform": {
   "^.+\\.vue$": "vue-jest",
   ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
   "^.+\\.tsx?$": "ts-jest"
  },
  "moduleNameMapper": {
   "^@/(.*)$": "<rootDir>/src/$1"
  },
  "snapshotSerializers": [
   "jest-serializer-vue"
  ],
  "testMatch": [
   "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"
  ],
  "testURL": "http://localhost/"
 }
}

首先从简单的正则字符串常量文件入手进行测试,结果非常顺利,没有任何报错。

随后开始对方案页面的Scheme.vue组件进行测试,重点在于验证一个computed属性。编写了三种具有代表性的测试用例后,运行yarn test:unit Scheme.test.ts,结果显示三个it测试用例均通过,但控制台却输出了红色的错误警告。

console.error node_modules/vue/dist/vue.runtime.common.js:589 [Vue warn]: Invalid prop: type check failed for prop "headerPic". Expected String, got Object.

排查发现,问题出在调用的一个子组件上,该组件期望接收一个headerPic属性作为图片的src地址。查看相关源码后,发现这里使用了require引入图片资源。


<SideNav :header-pic="require('../../assets/scheme/schemeSideNavPic.jpg')">

直觉上认为逻辑没有问题,通过vue-devtool检查,"/img/schemeSideNavPic.f988623b.jpg"确实显示为字符串,似乎并非require本身的问题。考虑到可能是Jest对require的处理机制导致,再次检查Jest配置,发现已经针对jpg等静态文件做了相应处理。查看jest-transform-stub模块的源码,实现非常简单。


module.exports = {
 process: function() {
  return ''
 }
}

既然该模块对静态文件返回空字符串且不做额外处理,按理说不应导致类型错误。借助VSCode强大的调试功能,尝试调试源码,但未能在Vue文件中触发断点。无奈之下,在TypeScript文件中添加const pic = require('../../../assets/scheme/schemeSideNavPic.jpg')语句,再次启动调试,发现实际获取到的值。

Vue项目单元测试背景与准备

正是jest-transform-stub模块的处理结果,确实是一个对象,这与命令行运行的结果一致。因此,需要调整处理方式,使其返回正确的字符串格式。


module.exports = ""

查阅Jest官方文档,搜索相关关键词,运气不错,moduleNameMapper选项完全能够满足处理静态文件的需求。


"moduleNameMapper": {
   "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
  }

fileMock.js内容如下:


// __mocks__/fileMock.js

module.exports = 'test-file-stub';

这意味着只要该模块返回字符串即可解决问题。在配置中加入moduleNameMapper后,测试完美通过。

组件交互与快照测试实战

接下来,对Scheme.vue组件发起模拟点击事件的测试。


const createScheme = wrapper.findAll('.sn-item').at(1)
createScheme.trigger('click')
expect((wrapper.vm as any).isCreateDialogShow).toBeTruthy()
expect(wrapper.find('.create-list-dialog').isVisible()).toBeTruthy()

使用Vue Test Utils的API获取createScheme元素,并触发其点击事件,验证isCreateDialogShow这个data值是否被正确设置为true。该项目使用了element-ui组件库。


<el-dialog
   :visible.sync="isCreateDialogShow"
   width="600px"
   class="create-list-dialog"
   title="创建方案">
   ...
</el-dialog>

由于此dialog可见性逻辑正确,测试顺利通过。

随后尝试新功能——快照测试,使用toMatchSnapshot方法也顺利通过了验证。

复杂场景:Axios插件Mock与数据模拟

接下来进行更复杂的测试,针对Login.vue登录页面。主要测试其调用接口并成功设置Store值的过程。由于不能走真实的网络接口(速度慢且结果不可预测),必须使用Mock数据。

项目中创建了axios.plugin.ts Vue插件,这该如何Mock呢?查阅官方文档,感觉Manual Mocks部分最合适,但官方示例并不适用于Vue插件的Mock。继续浏览文档,不知是受何启发还是突然开窍,应该是受fs模块的启发,突然明白了如何Mock插件。Mock一个模块只需模仿其类型即可,具体实现无关紧要。这个HTTP请求插件的Mock必须能返回期望的值,fs模块的__setMockFiles方法给了我启示,可以直接给接口的返回result设值。于是有了下面的实现。

__mocks__/axios.plugin.ts文件内容如下:


const MockAxios = {} as any

let result = {} as any
MockAxios.install = (Vue: any, options: any) => {
 Vue.prototype.$axios = function () {
  /* eslint-disable prefer-promise-reject-errors */
  return new Promise((resolve, reject) => {
   if (result.ResultCode === '200') {
    return result.Info
   } else {
    reject({ code: result.ResultCode, msg: result.Message, info: result.Info })
   }
  })
 }
}

MockAxios.__setMockData = (data: any) => {
 result = data
}

export default MockAxios

此后测试一路顺畅,执行localVue.use(Vuex)和localVue.use(AxiosPlugin)后,


const mockData = {
 ResultCode: '200',
 Msg: true,
 Info: {
  OpenId: 99,
  UserId: 92003,
 },
}
AxiosPlugin.__setMockData(mockData)
(wrapper.vm as any).login({ code: '29992' }).then(() => {
 expect(wrapper.vm.$store.state.userInfo.OpenId).toBe(mockData.Info.OpenId)
 expect(wrapper.vm.$store.state.userInfo.UserId).toBe(mockData.Info.UserId)
})

测试完美通过,Vue的单元测试框架基本搭建完成,可以向领导汇报进展了。

生成代码覆盖率报告

向领导展示成果时,还需要提供覆盖率报告。

yarn test:unit --coverage

当前覆盖的文件较少,未包含所有源文件。需要加入collectCoverageFrom配置项,至此整个单元测试体系就比较完备了。 下面是完整的Jest配置。


{
 "jest": {
  "moduleFileExtensions": [
   "js",
   "jsx",
   "json",
   "vue",
   "ts",
   "tsx"
  ],
  "transform": {
   "^.+\\.vue$": "vue-jest",
   ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
   "^.+\\.tsx?$": "ts-jest"
  },
  "moduleNameMapper": {
   "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
   "^@/(.*)$": "<rootDir>/src/$1"
  },
  "snapshotSerializers": [
   "jest-serializer-vue"
  ],
  "testMatch": [
   "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"
  ],
  "testURL": "http://localhost/",
  "collectCoverageFrom": [
   "**/*.{vue,ts}",
   "!**/node_modules/**",
   "!**/*.d.ts"
  ]
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章