vitest

📁 jezweb/claude-skills 📅 7 days ago
90
总安装量
90
周安装量
#2549
全站排名
安装命令
npx skills add https://github.com/jezweb/claude-skills --skill vitest

Agent 安装分布

claude-code 68
opencode 62
gemini-cli 60
codex 55
cursor 52

Skill 文档

Vitest – Modern Test Framework

Status: Production Ready Last Updated: 2026-02-06 Vitest Version: 4.x Vite Compatibility: 6.x


Quick Start

# Install
pnpm add -D vitest

# Add to package.json
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run",
    "test:coverage": "vitest run --coverage"
  }
}

Configuration

Minimal Config (vitest.config.ts)

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
  },
});

React Config

import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts'],
    css: true,
  },
});

Cloudflare Workers Config

import { defineConfig } from 'vitest/config';
import { cloudflare } from '@cloudflare/vite-plugin';

export default defineConfig({
  plugins: [cloudflare()],
  test: {
    globals: true,
    environment: 'node',
    // Workers tests often need longer timeouts for D1/KV
    testTimeout: 10000,
  },
});

Mocking Patterns

vi.mock – Module Mocking

import { vi, describe, it, expect } from 'vitest';
import { fetchUser } from './api';

// Mock entire module
vi.mock('./api', () => ({
  fetchUser: vi.fn(),
}));

describe('User component', () => {
  it('fetches user data', async () => {
    // Type-safe mock implementation
    vi.mocked(fetchUser).mockResolvedValue({ id: 1, name: 'Test' });

    // ... test code

    expect(fetchUser).toHaveBeenCalledWith(1);
  });
});

vi.spyOn – Spy on Methods

import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';

describe('Date handling', () => {
  beforeEach(() => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2026-01-01'));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('uses mocked date', () => {
    expect(new Date().getFullYear()).toBe(2026);
  });
});

vi.stubGlobal – Global Mocks

import { vi, describe, it, expect } from 'vitest';

describe('Environment', () => {
  it('mocks fetch globally', async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve({ data: 'test' }),
    });

    vi.stubGlobal('fetch', mockFetch);

    const response = await fetch('/api/test');
    expect(mockFetch).toHaveBeenCalledWith('/api/test');

    vi.unstubAllGlobals();
  });
});

Snapshot Testing

Basic Snapshots

import { describe, it, expect } from 'vitest';

describe('Component output', () => {
  it('matches snapshot', () => {
    const result = renderComponent({ title: 'Hello' });
    expect(result).toMatchSnapshot();
  });

  it('matches inline snapshot', () => {
    const result = { name: 'test', count: 42 };
    expect(result).toMatchInlineSnapshot(`
      {
        "count": 42,
        "name": "test",
      }
    `);
  });
});

Update Snapshots

# Update all snapshots
vitest run --update

# Interactive update
vitest --ui

In-Source Testing

Test code directly in source files (tree-shaken in production):

// src/utils/math.ts
export function add(a: number, b: number): number {
  return a + b;
}

// In-source test block
if (import.meta.vitest) {
  const { describe, it, expect } = import.meta.vitest;

  describe('add', () => {
    it('adds two numbers', () => {
      expect(add(1, 2)).toBe(3);
    });
  });
}

Config for in-source testing:

// vitest.config.ts
export default defineConfig({
  test: {
    includeSource: ['src/**/*.{js,ts}'],
  },
  define: {
    'import.meta.vitest': 'undefined', // Tree-shake in production
  },
});

Workspace Configuration (Monorepos)

// vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';

export default defineWorkspace([
  // Each package can have its own config
  'packages/*/vitest.config.ts',

  // Or define inline
  {
    test: {
      name: 'unit',
      include: ['src/**/*.test.ts'],
      environment: 'node',
    },
  },
  {
    test: {
      name: 'browser',
      include: ['src/**/*.browser.test.ts'],
      browser: {
        enabled: true,
        provider: 'playwright',
        name: 'chromium',
      },
    },
  },
]);

Browser Mode Testing

// vitest.config.ts
export default defineConfig({
  test: {
    browser: {
      enabled: true,
      provider: 'playwright', // or 'webdriverio'
      name: 'chromium',
      headless: true,
    },
  },
});
# Install browser provider
pnpm add -D @vitest/browser playwright

Coverage

# Install coverage provider
pnpm add -D @vitest/coverage-v8

# Run with coverage
vitest run --coverage
// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      exclude: [
        'node_modules/',
        'src/test/',
        '**/*.d.ts',
      ],
      thresholds: {
        statements: 80,
        branches: 80,
        functions: 80,
        lines: 80,
      },
    },
  },
});

Jest Migration

Key Differences

Jest Vitest
jest.fn() vi.fn()
jest.mock() vi.mock()
jest.spyOn() vi.spyOn()
jest.useFakeTimers() vi.useFakeTimers()
jest.clearAllMocks() vi.clearAllMocks()
@jest/globals vitest

Migration Steps

  1. Replace imports:
// Before (Jest)
import { jest } from '@jest/globals';

// After (Vitest)
import { vi } from 'vitest';
  1. Update config:
// jest.config.js → vitest.config.ts
export default defineConfig({
  test: {
    globals: true, // Enables describe/it/expect without imports
    environment: 'jsdom',
  },
});
  1. Replace jest. with vi.:
# Quick replace (review changes carefully)
find src -name "*.test.ts" -exec sed -i 's/jest\./vi./g' {} \;

Common Patterns

Testing Async Code

import { describe, it, expect } from 'vitest';

describe('async operations', () => {
  it('resolves promise', async () => {
    const result = await fetchData();
    expect(result).toBeDefined();
  });

  it('rejects with error', async () => {
    await expect(failingOperation()).rejects.toThrow('Expected error');
  });
});

Testing with Fixtures

import { describe, it, expect, beforeEach } from 'vitest';

describe('with fixtures', () => {
  let testData: TestData;

  beforeEach(() => {
    testData = createTestFixture();
  });

  it('uses fixture', () => {
    expect(testData.id).toBeDefined();
  });
});

Parameterized Tests

import { describe, it, expect } from 'vitest';

describe.each([
  { input: 1, expected: 2 },
  { input: 2, expected: 4 },
  { input: 3, expected: 6 },
])('double($input)', ({ input, expected }) => {
  it(`returns ${expected}`, () => {
    expect(double(input)).toBe(expected);
  });
});

Debugging

Run Single Test

vitest run -t "test name"
vitest run src/specific.test.ts

Debug Mode

# With Node inspector
node --inspect-brk ./node_modules/vitest/vitest.mjs run

# Or use Vitest UI
vitest --ui

Watch Mode

vitest          # Watch mode (default)
vitest run      # Single run
vitest watch    # Explicit watch

Troubleshooting

“Cannot find module” in mocks

// Ensure mock path matches import path exactly
vi.mock('./api'); // Matches: import { x } from './api'
vi.mock('../api'); // Different! Won't work for './api' imports

ESM/CJS Issues

// vitest.config.ts - for CJS dependencies
export default defineConfig({
  test: {
    deps: {
      inline: ['problematic-cjs-package'],
    },
  },
});

Globals Not Defined

// If using globals: true but TypeScript complains
// Add to tsconfig.json:
{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

See Also

  • testing-patterns skill – General testing patterns
  • testing-library skill – React Testing Library integration
  • Official docs: https://vitest.dev