Vue Test Utils实战技巧与常见问题避坑指南

Designer°振莉 框架 阅读 656
赞 22 收藏
二维码
手机扫码查看
反馈

先说结论,我基本只用 Vue Test Utils + Vitest

如果你问我现在测 Vue 组件选哪个方案,我会直接告诉你:Vue Test Utils 配合 Vitest。不是因为它是官方推荐,而是因为——我真的被其他方案折腾够了。

Vue Test Utils实战技巧与常见问题避坑指南

以前项目小的时候,用 Jest 搭 Vue Test Utils 还行。但自从我们项目上了 TypeScript、引入了大量自定义指令和动态组件后,Jest 的启动时间越来越离谱,CI 上跑一次单元测试要快 3 分钟。后来团队里有人提议换 Vitest,我一开始还觉得没必要,结果试了一周,彻底转粉了。

今天就想聊聊我在实际项目中对比过的几种主流组合,重点讲 Vue Test Utils 在不同环境下的表现,以及我踩过的那些坑。

谁更灵活?谁更省事?

Vue Test Utils 本身只是一个测试工具库,它不绑定运行时环境。你可以用它搭配 Jest、Mocha、Vitest 甚至 Karma。但真正影响体验的,其实是背后的测试运行器(test runner)和构建系统。

我主要对比三套组合:

  • Jest + Vue Test Utils
  • Mocha + @vue/test-utils + webpack
  • Vitest + Vue Test Utils

前两个是老派方案,第三个是近两年冒出来的黑马。

Jest:曾经的王者,现在有点重

说实话,Jest 曾经是我首选。配置一次能用半年,社区生态也完善。但问题是——太重了。

特别是当你用了 Vue 3 的 <script setup> 和 TypeScript 后,你需要额外装 ts-jestbabel-jest,还得处理 ESM 模块导入问题。下面是一个典型配置:

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  transform: {
    '^.+\.vue$': '@vue/vue3-jest',
    '^.+\.tsx?$': 'ts-jest',
    '^.+\.jsx?$': 'babel-jest'
  },
  moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'vue'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  }
}

看着挺标准对吧?但这个配置我在 Windows 上就翻过车:路径别名映射出错,报错信息还不清楚。折腾了半天发现是大小写敏感的问题。还有就是热更新慢得像蜗牛,改个测试文件要等 5 秒才跑起来。

而且最烦的是 mock 组件时语法啰嗦:

import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

describe('MyComponent', () => {
  it('renders correctly', () => {
    const wrapper = mount(MyComponent, {
      global: {
        mocks: {
          $route: { path: '/test' }
        },
        stubs: ['router-link', 'icon-button']
      }
    })

    expect(wrapper.text()).toContain('Hello Test')
  })
})

这种写法没问题,但一旦你要模拟 provide/inject 或者 pinia store,就得写一堆 setup 函数或者提前注册全局插件,非常繁琐。虽然功能全,但不够轻快。

Mocha 老古董?确实不太适合现代 Vue

几年前有些团队还在用 Mocha + karma-webpack 来跑 Vue 测试。说实话,这套我已经多年没碰了,上次是因为接手一个老项目被迫用了一下。

配置复杂到爆炸。你要搞懂 karma 的 launcher、webpack 的 alias、Babel 的 preset,还要手动引入 polyfill 才能让 globalThis 正常工作。跑个简单测试要启动浏览器实例,本地调试延迟感人。

代码倒是差不多:

it('should render title', () => {
  const wrapper = mount(MyComponent)
  expect(wrapper.find('h1').text()).toBe('Welcome')
})

看起来简洁,但实际上每个测试文件开头都要写一堆 import 和 setup,维护成本高。CI 环境还得装 Chrome Headless,时不时崩溃一下。我现在看到 karma.conf.js 文件都想绕着走。

结论很明确:除非你在维护十年以上的老项目,否则真没必要碰这套组合。

Vitest:丝滑到让我怀疑人生

第一次跑 Vitest 是同事拉我 review 代码时看到的。他随口说了句“你试试看这个,比 Jest 快十倍”,我不信邪,自己搭了个 demo 项目验证了一下——卧槽,真香。

首先安装简单:

npm install -D vitest @vue/test-utils jsdom

然后配个 vite.config.ts 就能跑:

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    transformMode: {
      web: [/.[tj]sx$/]
    }
  }
})

注意这里有个关键点:transformMode.web 让 Vitest 只把 Vue 文件交给 Vite 插件处理,JS/TS 文件默认由 esbuild 快速编译,速度起飞。

写测试也特别顺手:

// MyComponent.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import MyComponent from '../src/components/MyComponent.vue'

describe('MyComponent', () => {
  it('renders properly', () => {
    const wrapper = mount(MyComponent, {
      props: {
        title: 'Hello Vitest'
      }
    })

    expect(wrapper.text()).includes('Hello Vitest')
    expect(wrapper.find('button')).toBeTruthy()
  })
})

热重载几乎是即时的,保存即跑,开发体验完全不是一个级别。而且原生支持 ESM、TypeScript、HMR,几乎零配置就能跑起来。

我还试过在测试中调接口:

global.fetch = vi.fn(() =>
  Promise.resolve({ json: () => Promise.resolve({ id: 1 }) })
)

// 测试完成后清理
afterEach(() => {
  vi.clearAllMocks()
})

配合 vi.fn()vi.spyOn(),mock 起来比 Jest 更自然。而且 Vitest 自带 UI 界面,打开 localhost:3000 就能看到所有测试用例状态,排查失败用例一目了然。

我的选型逻辑

现在新项目我一律推 Vitest + Vue Test Utils。原因很简单:

  • 启动速度快,开发时幸福感爆棚
  • 配置极简,基本不用动就能跑 TS 和 Vue
  • 和 Vite 生态无缝衔接,别名、CSS、assets 都自动识别
  • API 兼容 Jest,迁移成本低

至于老项目要不要迁?看情况。如果现有 Jest 跑得好好的,没必要为了“新技术”硬切。但我建议新模块统一用 Vitest 写测试,逐步过渡。

有一个小坑要注意:如果你用了 Cypress Component Testing,它的底层也是基于 Vue Test Utils,但它依赖具体的框架适配器。这时候就不能随便换 runner 了。不过这是另一个话题了。

另外提一句,fetch 模拟这块,我之前在 Jest 里经常忘记清空 mock 导致测试污染,Vitest 的 vi.mock() 和作用域控制做得更好,减少了这类问题。

以上是我的对比总结,有不同看法欢迎评论区交流

我不是说 Jest 不好,只是它已经不适合我对“快速反馈”的要求了。前端这几年变化太快,测试工具也在进化。Vitest 抓住了“开发者体验”这个痛点,赢了。

Vue Test Utils 本身没啥大问题,API 设计合理,文档清晰。关键是它能在不同的 runner 上稳定工作。所以选择哪个方案,本质上是在选背后的支持体系。

我个人观点:不要死守旧技术。哪怕公司流程还没跟上,至少在自己的 demo 或工具库中尝试新方案。不然哪天突然让你优化 CI 时间,你会发现根本无从下手。

最后提醒一点:不管用哪个 runner,记得给你的组件测试加上覆盖率报告。我们现在的配置是:

{
  "test": {
    "coverage": {
      "provider": "istanbul",
      "reportsDirectory": "coverage",
      "lines": 80,
      "functions": 80,
      "branches": 70,
      "statements": 80
    }
  }
}

这样至少能避免写出一堆“通过但毫无意义”的测试。

这个技巧的拓展用法还有很多,后续会继续分享这类实战经验。以上是我踩坑后的总结,希望对你有帮助。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论