前端单元测试工具链实战:Vitest与Jest与Testing Library选型配置

前端单元测试为什么需要工具链

前端工程化成熟后,单测已经不是可选项。组件与业务逻辑规模起来后,手工回归成本指数上升,单元测试的价值是:改动一行公共函数后,能立即发现多少个组件被影响。Vitest与Jest是当下主流的两个单测框架,配套React Testing Library / Vue Test Utils(测试库),形成完整的”测试框架+渲染/交互库”组合。Vitest与Jest到底怎么选,关键看团队工程栈与构建工具。

Vitest与Jest对比:性能与生态差异

Vitest基于Vite构建,利用原生ESM与unenv的bundleless模式,测试速度比Jest在复杂项目上快2-5倍;Jest生态历史最久,对旧版CJS项目与复杂mock的兼容性强。核心差异:

  • 测试框架:Vitest不需要babel配置,直接从Vite配置读transform,Jest需要额外的babel-jest或ts-jest
  • 模块mocking:Vitest的vi.mock与Jest的jest.mock几乎一一对应,迁移成本低;但Vitest的vi.hoisted与spyOn在Vite生态下更顺
  • 并行与watch:Vitest默认启用HMR与并发文件执行,开发期反馈更实时
  • 覆盖率:Vitest内置v8/istanbul,Jest常用jest-junit+nyc

Vite项目优先Vitest(零额外配置);CRA等webpack旧项目保留Jest;两个项目混用不建议。若团队同时维护Vite与webpack项目,测试框架建议统一为Jest(兼容性最广),新项目新建时选Vitest。

Vitest在Vue3项目中的配置

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

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./tests/setup.ts'],
    include: ['src/**/*.{test,spec}.{js,ts}'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html'],
      include: ['src/**/*.{js,ts,vue}'],
    },
  },
})

Vue组件测试用@vue/test-utils,配合Vitest:

// tests/Button.spec.ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import MyButton from '@/components/MyButton.vue'

describe('MyButton', () => {
  it('点击触发emit事件且参数正确', async () => {
    const wrapper = mount(MyButton, { props: { label: '提交' } })
    await wrapper.get('button').trigger('click')
    expect(wrapper.emitted('click')).toHaveLength(1)
  })

  it('禁用状态下不响应点击', async () => {
    const wrapper = mount(MyButton, { props: { label: '提交', disabled: true } })
    await wrapper.get('button').trigger('click')
    expect(wrapper.emitted('click')).toBeUndefined()
  })
})

jsdom环境负责模拟DOM;setup.ts里可引入@testing-library/jest-dom的matcher扩展(toBeInTheDocument等),配合globals:true免去每个文件的import。

React项目用Testing Library与Vitest

// src/components/Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import Counter from './Counter'

describe('Counter', () => {
  it('点击按钮后计数增加', () => {
    const onIncrement = vi.fn()
    render(<Counter onIncrement={onIncrement} />)
    fireEvent.click(screen.getByRole('button', { name: /increment/i }))
    expect(screen.getByText('1')).toBeInTheDocument()
    expect(onIncrement).toHaveBeenCalledTimes(1)
  })

  it('mock fetch后渲染列表', async () => {
    vi.spyOn(global, 'fetch').mockResolvedValue({
      json: async () => [{ id: 1, name: 'alice' }],
    })
    render(<UserList />)
    expect(await screen.findByText('alice')).toBeInTheDocument()
    vi.restoreAllMocks()
  })
})

Testing Library强调按”用户可见方式”查询(getByRole/getByText),不推荐按组件内部className断言,避免测试与实现耦合。测试异步更新用findBy*或waitFor,不用setTimeout。

测试覆盖率与CI集成

Vitest覆盖率报告在CI中给出阈值卡点:

# package.json scripts
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest"
// vitest.coverage config
coverage: {
  statements: 80,
  branches: 70,
  functions: 80,
  lines: 80,
  exclude: ['src/main.ts', 'src/router/**'],
}

CI流水线里vitest run --coverage失败则阻塞合并,能强制团队保持基线。组件测试重点关注公共组件、hooks、工具函数、表单交互与错误分支;路由、样式类、第三方SDK适配层不必堆全覆盖,优先保证关键链路与回归价值高的模块。测试文件命名统一*.test.ts(x),Jest生态的test-utils可平滑迁移(render、screen等API相同)。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/qian-duan-dan-yuan-ce-shi-gong-ju-lian-shi-zhan-vitest-yu/

(0)
小编小编
上一篇 2小时前
下一篇 2小时前

相关推荐