mcpField
V
vitest

vitest

github.com/claude-dev-suite测试与质量4636 次安装2 天前更新
代码仓库

关于 vitest

面向 JavaScript 或 TypeScript 开发者的 Vitest 测试参考技能,涵盖测试结构、断言、模拟、覆盖率、CI 和故障排查。

主要功能

  1. 01组织测试套件与断言
  2. 02模拟函数和模块
  3. 03编写异步测试
  4. 04配置覆盖率与 CI
  5. 05测试组织与故障排查

适用场景

  1. 01为应用代码编写单元测试
  2. 02模拟 API 模块和函数
  3. 03为 CI 配置覆盖率报告

MCP 相关

该技能指向 `mcp__documentation__fetch_docs`,用于获取完整的 Vitest 文档。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill vitest

安装后在 Claude Code 中输入/vitest确认已加载。

附带文件quick-ref/coverage.mdquick-ref/mocking.md

详细介绍

Vitest 核心知识

深入了解:使用 mcp__documentation__fetch_docs,并指定技术:vitest,获取全面的文档。

不适用场景

  • 端到端测试 - 浏览器端端到端测试请使用 playwright 或 cypress
  • React 组件测试 - 组件测试请将此技能与 testing-library 结合使用
  • Jest 迁移 - 如果项目使用 Jest,请改用 jest 技能(语法相似,但并不完全相同)
  • API 集成测试 - 请使用特定框架的集成测试技能(如 spring-boot-integration 等)

基本测试

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { calculateTotal } from './utils';

describe('calculateTotal', () => {
  it('should sum array of numbers', () => {
    expect(calculateTotal([1, 2, 3])).toBe(6);
  });

  it('should return 0 for empty array', () => {
    expect(calculateTotal([])).toBe(0);
  });
});

匹配器

// Equality
expect(value).toBe(exact);
expect(value).toEqual(deepEqual);
expect(value).toMatchObject(partial);

// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeDefined();

// Numbers
expect(value).toBeGreaterThan(n);
expect(value).toBeCloseTo(0.3, 5);

// Strings
expect(str).toMatch(/pattern/);
expect(str).toContain('substring');

// Arrays
expect(arr).toContain(item);
expect(arr).toHaveLength(3);

// Errors
expect(() => fn()).toThrow();
expect(() => fn()).toThrowError('message');

模拟

// Mock function
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ data: [] });

// Mock module
vi.mock('./api', () => ({
  fetchUsers: vi.fn().mockResolvedValue([])
}));

// Spy
const spy = vi.spyOn(object, 'method');
expect(spy).toHaveBeenCalledWith('arg');

// Reset
vi.clearAllMocks();
vi.resetAllMocks();

异步测试

it('should fetch data', async () => {
  const data = await fetchUsers();
  expect(data).toHaveLength(3);
});

it('should reject', async () => {
  await expect(fetchFail()).rejects.toThrow('Error');
});

配置

// vitest.config.ts
export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    coverage: { provider: 'v8', reporter: ['text', 'html'] }
  }
});

生产就绪

测试组织
// Proper test structure
describe('UserService', () => {
  // Setup/teardown at appropriate level
  let service: UserService;
  let mockDb: MockedObject<Database>;

  beforeAll(async () => {
    // Expensive setup once
    await setupTestDatabase();
  });

  beforeEach(() => {
    // Reset state before each test
    mockDb = vi.mocked(new Database());
    service = new UserService(mockDb);
    vi.clearAllMocks();
  });

  afterAll(async () => {
    // Cleanup
    await teardownTestDatabase();
  });

  describe('create', () => {
    it('should create user with valid data', async () => {
      // Arrange
      const input = { name: 'John', email: '[email protected]' };
      mockDb.insert.mockResolvedValue({ id: '1', ...input });

      // Act
      const result = await service.create(input);

      // Assert
      expect(result).toMatchObject(input);
      expect(mockDb.insert).toHaveBeenCalledWith('users', input);
    });

    it('should throw on duplicate email', async () => {
      mockDb.insert.mockRejectedValue(new UniqueConstraintError());
      await expect(service.create(input)).rejects.toThrow('Email already exists');
    });
  });
});
覆盖率配置
// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      exclude: [
        'node_modules/',
        'test/',
        '**/*.d.ts',
        '**/*.config.*',
        '**/types/**',
      ],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 75,
        statements: 80,
      },
    },
  },
});
CI 配置
# GitHub Actions
- name: Run tests
  run: npm run test:ci

- name: Upload coverage
  uses: codecov/codecov-action@v3
  with:
    files: ./coverage/lcov.info
    fail_ci_if_error: true
// package.json scripts
{
  "scripts": {
    "test": "vitest",
    "test:ci": "vitest run --coverage --reporter=junit --outputFile=test-results.xml",
    "test:watch": "vitest --watch"
  }
}
监控指标
指标目标
行覆盖率> 80%
分支覆盖率> 75%
测试执行时间< 60s
不稳定测试比率0%
检查清单
  • Arrange-Act-Assert 模式
  • 独立测试(无共享状态)
  • 有意义的测试描述
  • 强制执行覆盖率阈值
  • CI/CD 集成
  • 测试中不使用 console.log
  • 测试之间重置模拟
  • 正确等待异步测试完成
  • 覆盖边界情况
  • 测试错误路径

反模式

反模式不良影响解决方案
测试实现细节重构时测试会失败测试行为,而非内部实现
在测试之间共享状态测试不稳定,且依赖执行顺序使用 beforeEach,隔离测试设置
随意等待(setTimeout)测试缓慢且不可靠使用 waitFor 或异步工具
不重置模拟前一个测试会影响下一个测试在 beforeEach 中调用 vi.clearAllMocks()
测试私有方法与实现紧密耦合通过公共 API 进行测试
一个庞大的测试难以调试失败原因每个测试只做一个断言(理想情况)
不测试错误情况生产环境中的错误可能漏网测试正常路径和错误路径

快速故障排除

问题可能原因解决方案
"Cannot find module"缺少模拟设置检查 vi.mock() 路径是否与导入路径一致
测试超时未等待异步操作确保所有异步操作都使用 await
"Expected 0 calls, received 1"模拟未清除在 beforeEach 中添加 vi.clearAllMocks()
测试不稳定共享状态或时序问题隔离测试设置,避免使用 setTimeout
覆盖率未更新缓存问题使用 --no-cache 标志运行
"TypeError: vi.fn is not a function"缺少导入从 'vitest' 导入 { vi }

参考文档

评论

支持 Markdown