Claude
Skills
Sign in
Back

vitest-v4

Included with Lifetime
$97 forever

Vitest 4+ testing with Vite. Use when configuring vitest.config.ts, writing unit/integration/browser tests, implementing mocks with vi.fn/vi.spyOn/vi.mock, setting up V8 or Istanbul coverage, or migrating from Jest or older Vitest workspace setups. Triggers on vitest, vitest.config.ts, vi.mock, browser mode, vitest/browser, projects, setupFiles, and toMatchScreenshot.

Writing & Docs

What this skill does


# Vitest 4 Testing Skill

> Write, configure, and debug Vitest 4 test suites with Vite-native patterns.

## Before You Start

**This skill prevents 7+ common Vitest 4 mistakes and saves ~50% tokens.**

| Metric | Without Skill | With Skill |
|--------|--------------|------------|
| Setup Time | ~90 min | ~30 min |
| Common Errors | 7+ | 0 |
| Token Usage | High (trial/error) | Low (known patterns) |

### Known Issues This Skill Prevents

1. Hanging agent runs from using watch mode instead of `vitest run`
2. Broken coverage configs from using removed `coverage.all` or `coverage.extensions`
3. Browser Mode spying failures from sealed ESM namespace objects
4. Mock leakage between tests from missing restore/reset config
5. Invalid multi-project setup from using deprecated `workspace` terminology
6. Wrong APIs from mixing Jest helpers into Vitest tests
7. Flaky browser interactions from using synthetic helpers instead of `vitest/browser`
8. Slow or unstable large suites from choosing the wrong execution pool or isolation mode

## Quick Start

### Step 1: Configure Vitest 4 for agent-safe runs

```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node',
    restoreMocks: true,
    clearMocks: true,
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
  },
});
```

**Why this matters:** Vitest 4 removed `coverage.all` and `coverage.extensions`, and agent/CI environments need one-shot execution plus automatic mock cleanup.

### Step 2: Write tests with Vitest APIs, not Jest APIs

```typescript
import { describe, expect, it, vi } from 'vitest';
import { addUser } from './add-user';
import * as api from './api';

describe('addUser', () => {
  it('returns the created user', async () => {
    vi.spyOn(api, 'createUser').mockResolvedValue({ id: '1', name: 'Ada' });

    await expect(addUser('Ada')).resolves.toEqual({ id: '1', name: 'Ada' });
  });
});
```

**Why this matters:** `vi` is the supported mocking API. Mixing `jest.fn()` or Jest-only patterns causes confusing failures and poor autocomplete.

> **Import rule:** Import `describe`, `it`, `expect`, and `vi` from `vitest` unless the project explicitly enables `globals: true`.

### Step 3: Use the correct runtime command

```bash
vitest run
vitest run --coverage
vitest run path/to/example.test.ts
```

**Why this matters:** `vitest` without `run` starts watch mode by default in development, which is a poor fit for agents, CI, and non-interactive verification.

## Critical Rules

### Always Do

- Use `vitest run` or `vitest --no-watch` for agent and CI workflows
- Prefer `vi.mock(import('./module'))` for type-safe module mocks
- Configure `restoreMocks`, `clearMocks`, or `mockReset` intentionally
- Use `projects` for multi-project configs; the rename began in Vitest 3.2 and older workspace-file usage is removed in Vitest 4
- Use a shared base config when multiple `projects` need common settings; projects do not inherit root config unless you opt in
- Use `coverage.include` to report on untested source files
- Use `page` and `userEvent` from `vitest/browser` in Browser Mode
- Prefer `forks` when native modules or runtime compatibility matter more than raw speed
- Share `vitest.config.ts` and the implementation file when asking AI to generate tests

### Never Do

- Never use `jest.fn`, `jest.spyOn`, or Jest-only globals in Vitest code
- Never rely on removed `coverage.all` or `coverage.extensions` in Vitest 4
- Never use plain watch mode for agent-driven verification
- Never use `vi.spyOn` on native ESM exports in Browser Mode
- Never forget that `vi.mock()` is hoisted before the rest of the file executes
- Never leave env/global stubs un-restored across tests

### Common Mistakes

**Wrong - removed coverage option:**
```typescript
export default defineConfig({
  test: {
    coverage: {
      all: true,
    },
  },
});
```

**Correct - use include globs:**
```typescript
export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
  },
});
```

**Why:** Vitest 4 removed `coverage.all` and `coverage.extensions`; `coverage.include` is the supported way to include uncovered files.

**Wrong - Browser Mode spy on sealed export:**
```typescript
import * as math from './math';
import { vi } from 'vitest';

vi.spyOn(math, 'add').mockReturnValue(10);
```

**Correct - use spy-enabled module mock:**
```typescript
import { vi } from 'vitest';

vi.mock(import('./math'), { spy: true });
```

**Why:** Native browser ESM namespace objects are sealed, so direct spies on exports fail in Browser Mode.

## Known Issues Prevention

| Issue | Root Cause | Solution |
|-------|-----------|----------|
| Tests never exit | Watch mode started in a non-interactive session | Use `vitest run` |
| Coverage report misses untested files | `coverage.include` not configured | Add explicit source globs |
| Browser Mode spy throws or does nothing | `vi.spyOn` used on sealed ESM exports | Use `vi.mock(import('./mod'), { spy: true })` |
| Mocks leak between tests | Cleanup flags missing | Enable `restoreMocks` / `clearMocks` / `unstubEnvs` |
| Multi-project config breaks after upgrade | Deprecated workspace terminology or removed workspace-file patterns carried over | Switch to `projects` and `defineProject` |
| Worker or pool config stops working | Old `maxThreads`, `maxForks`, or `poolOptions` carried forward | Migrate to Vitest 4 worker settings such as `maxWorkers` |
| Project-specific config unexpectedly disappears | Root config assumptions are not inherited into `projects` | Use `extends: true`, `mergeConfig`, or a shared base explicitly |
| AI-generated tests use wrong helpers | Jest patterns copied into Vitest | Replace with `vi`, Vitest imports, and Vitest matchers |
| Browser tests hang | Blocking dialogs or wrong user-event utilities | Mock dialogs and use `vitest/browser` helpers |
| Fast pool causes strange native-module failures | `threads` chosen for a suite that needs process isolation | Switch to `forks` or narrow thread usage |

## Bundled Resources

### References

- **Mocking rules and hoisting** → [`references/mocking-reference.md`](references/mocking-reference.md)
- **Browser Mode providers and pitfalls** → [`references/browser-mode-reference.md`](references/browser-mode-reference.md)
- **Coverage and multi-project config** → [`references/coverage-projects-reference.md`](references/coverage-projects-reference.md)
- **Pools, isolation, and persistent cache** → [`references/pools-execution-reference.md`](references/pools-execution-reference.md)
- **Reference index** → [`references/README.md`](references/README.md)

## Configuration Reference

### vitest.config.ts

```typescript
import { defineConfig, defineProject } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';

export default defineConfig({
  test: {
    projects: [
      defineProject({
        test: {
          name: 'unit',
          include: ['src/**/*.test.ts'],
          environment: 'node',
        },
      }),
      defineProject({
        test: {
          name: 'browser',
          include: ['src/**/*.browser.test.ts'],
          browser: {
            enabled: true,
            provider: playwright(),
            instances: [{ browser: 'chromium' }],
          },
        },
      }),
    ],
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{ts,tsx}'],
    },
    restoreMocks: true,
    unstubEnvs: true,
    setupFiles: ['./test/setup.ts'],
  },
});
```

**Key settings:**
- `test.projects`: Stable multi-project terminology; the rename started in Vitest 3.2, and projects do not automatically inherit every root config value, so shared settings should be factored into a reused base when needed
- `coverage.include`: Required when uncovered source files must appear in the report
- `browser.provider`: In Vitest 4, import the provider factory from the pro

Related in Writing & Docs