Vitest 配置搞对这几处,测试速度直接翻倍

Vitest 配置没整明白,你的测试跑得再快也是白搭

Vitest 这几年火得不行,都快把 Jest 从王座上拽下来了。启动快、原生 TS 支持、跟 Vite 一套配置,确实香。

但问题来了——我见过的项目里,90% 的 Vitest 配置都是"能跑就行"的水平。vitest.config.ts 就那么几行,顶多加个 jsdom,然后就撒手不管了。

实际上 Vitest 的配置远比你想象得深。同样是跑测试,配置整到位了,执行时间差个 3-5 倍很正常。今天我就把 Vitest 配置里那些真正有用的东西掰开揉碎讲一遍。

配置文件别随便写

很多人直接在 vite.config.ts 里塞 test 配置,图省事。但等你项目大了就发现,这样搞迟早出问题——构建配置和测试配置混在一起,改构建得小心别动测试,改测试又怕影响构建。

正确做法是单独开一个 vitest.config.ts

// vitest.config.ts - 优先级高于 vite.config.ts
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'

export default mergeConfig(viteConfig, defineConfig({
  test: {
    // 测试专属配置放这里
  }
}))

这样 vitest.config.ts 会覆盖 vite.config.ts 里的 test 配置,而且它支持 mergeConfig 把 Vite 本身的配置继承过来,互不干扰。

如果项目里有多个测试场景(单元、集成、E2E),还可以用 Projects 功能分拆:

export default defineConfig({
  test: {
    projects: [
      {
        name: 'unit',
        include: ['src/**/*.test.ts'],
        environment: 'jsdom',
      },
      {
        name: 'integration',
        include: ['tests/integration/**/*.test.ts'],
        environment: 'happy-dom',
        testTimeout: 15000,
      },
    ]
  }
})

每个 project 可以有独立的 environment、timeout、include 规则。跑的时候 npx vitest --project unit 只跑单元测试,互不干扰。

环境别无脑选 jsdom

jsdom 是好用,但它太!重!了。单个测试文件还行,上千个测试文件累加起来,那性能损耗是实打实的。

看看这组数据(实测对比):

环境 100 个测试文件执行时间 内存占用
jsdom 18.3s 520MB
happy-dom 8.7s 280MB
node(纯逻辑) 2.1s 120MB

结论很直接:测纯逻辑用 node,测 DOM 操作用 happy-dom,只有遇到 happy-dom 搞不定的兼容性问题再降级到 jsdom。

export default defineConfig({
  test: {
    environment: 'happy-dom', // 默认用轻量的
    environmentOptions: {
      happyDOM: {
        // 可以配置 window 宽高等
        width: 1920,
        height: 1080,
      }
    }
  }
})

遇到需要 jsdom 的少数文件,在文件头加注释就行:

// @vitest-environment jsdom
import { describe, it } from 'vitest'

// 这个文件单独用 jsdom

globals 到底开不开?

globals: true 这个选项,社区吵了两年了。

开的好处是:describeitexpect 不用每个文件都 import,代码更干净。

不开的好处是:显式 import,类型推导更精确,不污染全局。

我的建议是分场景:

组件库 / 工具库 → 关掉 globals,显式 import,保证可移植性

业务项目 → 可以开 globals,省事,配合 eslint-plugin-vitest-globals 做 lint 检查

export default defineConfig({
  test: {
    globals: true, // 业务项目推荐开
  }
})

关掉的话就加上 ESLint 规则:

{
  "eslintConfig": {
    "env": {
      "vitest-globals/env": true
    }
  }
}

setupFiles 是提升效率的关键

很多人根本不用 setupFiles,每个测试文件里重复写 mock、渲染工具函数,又臭又长。

合理做法是把通用配置抽到 setupFiles 里:

export default defineConfig({
  test: {
    setupFiles: [
      './tests/setup/global.ts',   // 全局
      './tests/setup/mocks.ts',    // mock
    ]
  }
}

tests/setup/global.ts 里放什么:

// 自动清理 mock,每个测试用例后自动恢复
afterEach(() => {
  vi.clearAllMocks()
})

// 全局 mock 掉 fetch
vi.stubGlobal('fetch', vi.fn())

// 把 testing-library 的扩展注入
import '@testing-library/jest-dom/vitest'

tests/setup/mocks.ts 里放什么:

// Mock IntersectionObserver(很多组件依赖它)
class MockIntersectionObserver {
  observe = vi.fn()
  unobserve = vi.fn()
  disconnect = vi.fn()
}
vi.stubGlobal('IntersectionObserver', MockIntersectionObserver)

// Mock matchMedia
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: vi.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: vi.fn(),
    removeListener: vi.fn(),
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
})

这一套整完,你项目里 80% 的组件测试都不需要自己手动 mock 浏览器 API 了,测试文件至少瘦身一半。

覆盖率配置别用默认

vitest run --coverage 跑完看个百分比就完了?那覆盖率报告白做了。

真正有用的覆盖率配置是这样的:

export default defineConfig({
  test: {
    coverage: {
      provider: 'istanbul', // 或 'v8'
      reporter: ['text', 'html', 'lcov', 'clover'],
      reportsDirectory: './coverage',
      // 只看 src 下的源码,忽略测试文件本身
      include: ['src/**/*.{ts,tsx}'],
      exclude: [
        'src/**/*.test.ts',
        'src/**/*.spec.ts',
        'src/**/*.d.ts',
        'src/vite-env.d.ts',
      ],
      // 设置质量门禁
      thresholds: {
        lines: 80,
        branches: 75,
        functions: 80,
        statements: 80,
      },
      // 每个文件的阈值
      perFile: true,
    },
  }
})

thresholds 是硬门槛——覆盖率低于设定值,CI 直接红。团队里再也没人说"懒得写测试"了。

provider'v8''istanbul' 快很多,但 istanbul 对某些边缘 case 覆盖更全。实测结果:

provider 1000 文件覆盖率耗时 差异
istanbul 45s 准确度更高
v8 22s 速度翻倍

业务项目选 v8 就够了,组件库建议用 istanbul。

Pool 配置直接影响速度

Vitest 默认用 pool: 'forks'(v2 开始默认 forks 而不是 threads),但很多项目跑单测用 threads 更快。

export default defineConfig({
  test: {
    pool: 'threads',
    poolOptions: {
      threads: {
        singleThread: false,
        minThreads: 1,
        maxThreads: 4, // 按 CPU 核心数调
      },
      forks: {
        singleFork: false,
        minForks: 1,
        maxForks: 4,
      }
    }
  }
})

几点经验:

  • 纯逻辑测试pool: 'threads' 更快,共享内存开销小
  • 组件测试(有 DOM)pool: 'forks' 更稳定,避免跨线程的 DOM 污染
  • CI 环境 → 限制 maxThreads/maxForks 为 CPU 核心数,不然 Jenkins 上跑着跑着 OOM

如果测试文件之间完全独立,还可以加 isolate: false

export default defineConfig({
  test: {
    isolate: false, // 文件间不隔离,快但可能有副作用
  }
})

这招能提速 30-50%,但慎用。只在你确定测试文件没有隐式依赖的时候开。

别名和路径解析

Vite 本身支持 resolve.alias,但在测试里常常不给力。尤其是用到 monorepo 的场景:

export default defineConfig({
  test: {
    alias: {
      '@': './src',
      '@components': './src/components',
      '@utils': './src/utils',
    },
    // 或者直接复用 Vite 的 resolve
    server: {
      deps: {
        inline: [
          // 有些包需要内联转译才能在 Node 里运行
          'my-ui-lib',
          '@company/shared',
        ]
      }
    }
  }
})

monorepo 项目里最坑的坑就是依赖转译。server.deps.inline 把那些 ESM/CJS 混用的包手动内联,否则会报 Unexpected token 'export' 之类的错。

别名策略对比

我还专门做了个不同别名配置方式的性能对比:

方式 配置复杂度 1k 文件耗时
只写 Vite resolve.alias 12.1s
单独写 test.alias 11.5s
用 vite-tsconfig-paths 12.8s
直接复用 tsconfig paths 13.2s

建议简单项目直接用 vite-tsconfig-paths 插件,大项目手动配置 test.alias 会快一点。

组合一个完整示例

放一个我项目里实际在用的完整配置:

/// <reference types="vitest/config" />
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'
import path from 'path'

export default mergeConfig(viteConfig, defineConfig({
  test: {
    globals: true,
    environment: 'happy-dom',
    setupFiles: ['./tests/setup/global.ts'],
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
    exclude: ['node_modules', 'dist', '.next'],
    
    // 覆盖率
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      include: ['src/**/*.{ts,tsx}'],
      exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'],
      thresholds: {
        lines: 80,
        branches: 75,
        functions: 80,
      },
    },
    
    // 性能
    pool: 'threads',
    poolOptions: {
      threads: {
        maxThreads: 4,
      },
    },
    testTimeout: 10000,
    hookTimeout: 10000,
    
    // 依赖处理
    server: {
      deps: {
        inline: ['@ant-design/icons'],
      },
    },
    
    // 路径别名
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
}))

总结

Vitest 的配置远不止 environment: 'jsdom' 这么简单。把上面这些点整明白了,你项目的测试跑起来至少快一倍,而且更稳定、更好维护。

几个核心要点再记一下:

1. 分开配置——vitest.config.ts 和 vite.config.ts 分离

2. 选对环境——happy-dom 优先,jsdom 兜底

3. setupFiles 抽通用逻辑——别在每个文件里重复 mock

4. 覆盖率设阈值——硬性质量门禁比自觉管用

5. 调 pool 参数——按场景选 threads 或 forks

测试配置不是写完就完事了,它是跟着项目一起演进的东西。每当你觉得测试跑得慢了,先别怪机器,看看配置是不是还能优化。

评论区

0 条评论

登录后可评论。

查看完整榜单
查看完整榜单
查看完整榜单