Vue 测试深度指南:Vitest + Vue Test Utils + Playwright E2E 与 CI 集成

Vue 3 应用从单元测试到 E2E 的完整测试策略:Vitest + Vue Test Utils 组件测试(mount/emits/slots/async)、Pinia Store Mocking、MSW API 拦截、Playwright 端到端测试、Cypress 组件测试、覆盖率标准与 GitHub Actions CI 集成。

测试是 Vue 应用质量保障的核心环节。Vue 生态提供了 Vue Test Utils(官方组件测试库)与 Vitest(Vite 原生测试框架)的无缝集成。本文覆盖从组件单元测试到 Playwright E2E 的完整测试体系。


一、测试环境配置

npm install -D vitest @vue/test-utils jsdom @testing-library/jest-dom
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './tests/setup.ts',
  },
})

// tests/setup.ts
import '@testing-library/jest-dom'

二、组件测试:Vue Test Utils

2.1 基础挂载与查询

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'

describe('Counter', () => {
  it('renders initial count', () => {
    const wrapper = mount(Counter, {
      props: { initial: 10 },
    })
    expect(wrapper.text()).toContain('10')
  })

  it('increments when clicked', async () => {
    const wrapper = mount(Counter)
    await wrapper.find('button').trigger('click')
    expect(wrapper.text()).toContain('1')
  })
})

2.2 Props 与 Emits 测试

it('emits update event', async () => {
  const wrapper = mount(InputComponent, {
    props: { modelValue: '' },
  })

  await wrapper.find('input').setValue('hello')

  expect(wrapper.emitted('update:modelValue')).toBeTruthy()
  expect(wrapper.emitted('update:modelValue')[0]).toEqual(['hello'])
})

2.3 Slots 测试

it('renders default slot', () => {
  const wrapper = mount(Modal, {
    slots: {
      default: '<p>Modal content</p>',
      footer: '<button>OK</button>',
    },
  })

  expect(wrapper.html()).toContain('Modal content')
})

2.4 异步组件测试

import { flushPromises } from '@vue/test-utils'

it('loads async data', async () => {
  const wrapper = mount(UserProfile)

  // 等待所有 Promise 解析
  await flushPromises()

  expect(wrapper.text()).toContain('Alice')
})

三、Pinia Store Mocking

import { setActivePinia, createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'

beforeEach(() => {
  setActivePinia(createPinia())
})

it('updates user state', () => {
  const store = useUserStore()
  store.login({ name: 'Alice' })

  expect(store.isLoggedIn).toBe(true)
  expect(store.user?.name).toBe('Alice')
})

四、MSW:API Mocking

// tests/mocks/handlers.ts
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('/api/user', () => {
    return HttpResponse.json({ id: '1', name: 'Alice' })
  }),
]

// tests/setup.ts
import { setupServer } from 'msw/node'
import { handlers } from './mocks/handlers'

const server = setupServer(...handlers)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

五、Playwright E2E

import { test, expect } from '@playwright/test'

test('user login flow', async ({ page }) => {
  await page.goto('/login')
  await page.fill('[name="email"]', 'test@example.com')
  await page.fill('[name="password"]', 'password')
  await page.click('button[type="submit"]')
  await expect(page).toHaveURL('/dashboard')
})

六、CI 集成

# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run test:unit -- --coverage
      - run: npm run test:e2e

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「frontend」更多文章