Source profileQuality 92/100Review permissions

PramodDutta/qaskills/seed-skills/vitest-testing/SKILL.md

Vitest Testing

Write fast unit and integration tests with Vitest — vitest.config.ts setup, vi.fn and vi.mock module mocking, fake timers, snapshots, V8 coverage with thresholds, workspaces for monorepos, and in-source testing.

Source repository stars
211
Declared platforms
4
Static risk flags
1
Last source update
2026-08-28
Source checked
2026-08-28

Decision brief

What it does: where it fits

This skill makes an AI agent write and configure Vitest test suites: a correct vitest.config.ts, module mocking with vi.mock and the vi.hoisted escape hatch, spies and fake timers, inline snapshots, V8 coverage gates, and projects config for monorepos. Trigger it on any Vite-bas…

Best for

    Not for

    • Referencing top-level variables inside a vi.mock factory. Hoisting makes them undefined at factory time — the error message mentions hoisting, believe it. Use vi.hoisted.
    • globals: true plus missing TS types. If you enable globals, add "types": ["vitest/globals"] to tsconfig, or imports break silently in editors.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexDeclaredSource recordInstall path and trigger
    Claude CodeDeclaredSource recordInstall path and trigger
    CursorDeclaredSource recordInstall path and trigger
    Gemini CLIDeclaredSource recordInstall path and trigger
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/vitest-testing"
    Safe inspection promptEditorial

    Inspect the Agent Skill "Vitest Testing" from https://github.com/PramodDutta/qaskills/blob/fb3fbec70591bad971dd97c5d9add6eaa99bae18/seed-skills/vitest-testing/SKILL.md at commit fb3fbec70591bad971dd97c5d9add6eaa99bae18. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Setup

      Watch mode is the default vitest command and only reruns tests affected by the changed module graph — keep it running while developing.

      Watch mode is the default vitest command and only reruns tests affected by the changed module graph — keep it running while developing.
    2. 02

      Core Principles

      1. Vitest reuses your Vite config — do not duplicate resolution logic. Aliases, plugins, and transforms from vite.config.ts apply to tests automatically. A separate Babel/transform setup is a Jest habit; drop it. 2. vi.mock is hoisted; factory variables are not. The mock factory…

      Vitest reuses your Vite config — do not duplicate resolution logic. Aliases, plugins, and transforms from vite.config.ts apply to tests automatically. A separate Babel/transform setup is a Jest habit; drop it.vi.mock is hoisted; factory variables are not. The mock factory runs before imports, so referencing top-level variables inside it throws. Use vi.hoisted() when the factory needs shared handles.Prefer vi.fn injected via parameters over vi.mock of whole modules. Module mocking is a sledgehammer; dependency injection keeps tests honest and refactor-safe.
    3. 03

      Patterns

      Partial mocks keep the rest of a module real:

      Partial mocks keep the rest of a module real:In-source tests for small internal utilities (stripped from production builds by define: { 'import.meta.vitest': 'undefined' }):
    4. 04

      vi.fn, Spies, and Dependency Injection

      Review the “vi.fn, Spies, and Dependency Injection” section in the pinned source before continuing.

      Review and apply the “vi.fn, Spies, and Dependency Injection” source section.
    5. 05

      vi.mock with vi.hoisted (the hoisting trap, solved)

      Partial mocks keep the rest of a module real:

      Partial mocks keep the rest of a module real:

    Permission review

    Static risk signals and limitations

    Runs scripts

    medium · line 18

    The documentation asks the agent to run terminal commands or scripts.

    npm install --save-dev vitest @vitest/coverage-v8

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score92/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars211SourceRepository attention, not individual Skill quality
    Compatibility4 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    PramodDutta/qaskills
    Skill path
    seed-skills/vitest-testing/SKILL.md
    Commit
    fb3fbec70591bad971dd97c5d9add6eaa99bae18
    License
    MIT
    Collected
    2026-08-28
    Default branch
    main
    View the original SKILL.md

    Vitest Testing

    This skill makes an AI agent write and configure Vitest test suites: a correct vitest.config.ts, module mocking with vi.mock and the vi.hoisted escape hatch, spies and fake timers, inline snapshots, V8 coverage gates, and projects config for monorepos. Trigger it on any Vite-based project, any repo with vitest in devDependencies, or when migrating from Jest.

    Core Principles

    1. Vitest reuses your Vite config — do not duplicate resolution logic. Aliases, plugins, and transforms from vite.config.ts apply to tests automatically. A separate Babel/transform setup is a Jest habit; drop it.
    2. vi.mock is hoisted; factory variables are not. The mock factory runs before imports, so referencing top-level variables inside it throws. Use vi.hoisted() when the factory needs shared handles.
    3. Prefer vi.fn injected via parameters over vi.mock of whole modules. Module mocking is a sledgehammer; dependency injection keeps tests honest and refactor-safe.
    4. Inline snapshots over file snapshots for small values. toMatchInlineSnapshot puts the expectation in the test where reviewers see it; file snapshots get blindly --updated.
    5. Coverage thresholds live in config and fail the run. A coverage report nobody gates on is wallpaper. Gate lines, functions, and branches — branch coverage is where the bugs hide.
    6. Use the default node environment unless you render DOM. jsdom/happy-dom cost startup time per file; set them per-file with a docblock, not globally.

    Setup

    npm install --save-dev vitest @vitest/coverage-v8
    
    // vitest.config.ts
    import { defineConfig } from 'vitest/config';
    
    export default defineConfig({
      test: {
        globals: false, // explicit imports; keeps files greppable and TS-clean
        environment: 'node',
        include: ['src/**/*.test.ts'],
        setupFiles: ['./test/setup.ts'],
        restoreMocks: true, // undo spy implementations between tests
        coverage: {
          provider: 'v8',
          reporter: ['text', 'lcov', 'html'],
          include: ['src/**'],
          exclude: ['src/**/*.test.ts', 'src/types/**', 'src/main.ts'],
          thresholds: {
            lines: 85,
            functions: 85,
            branches: 75,
            statements: 85,
          },
        },
      },
    });
    
    // package.json scripts
    {
      "scripts": {
        "test": "vitest run",
        "test:watch": "vitest",
        "test:coverage": "vitest run --coverage"
      }
    }
    

    Watch mode is the default vitest command and only reruns tests affected by the changed module graph — keep it running while developing.

    Patterns

    vi.fn, Spies, and Dependency Injection

    // src/notifier.ts
    export type SendEmail = (to: string, subject: string) => Promise<void>;
    
    export async function notifyOnFailure(
      jobName: string,
      failures: number,
      sendEmail: SendEmail,
    ): Promise<boolean> {
      if (failures === 0) return false;
      await sendEmail('[email protected]', `${jobName} failed ${failures} times`);
      return true;
    }
    
    // src/notifier.test.ts
    import { describe, expect, it, vi } from 'vitest';
    import { notifyOnFailure } from './notifier';
    
    describe('notifyOnFailure', () => {
      it('emails oncall with the failure count in the subject', async () => {
        const sendEmail = vi.fn().mockResolvedValue(undefined);
    
        const sent = await notifyOnFailure('nightly-sync', 3, sendEmail);
    
        expect(sent).toBe(true);
        expect(sendEmail).toHaveBeenCalledExactlyOnceWith(
          '[email protected]',
          'nightly-sync failed 3 times',
        );
      });
    
      it('stays silent when there are no failures', async () => {
        const sendEmail = vi.fn();
        await expect(notifyOnFailure('nightly-sync', 0, sendEmail)).resolves.toBe(false);
        expect(sendEmail).not.toHaveBeenCalled();
      });
    });
    

    vi.mock with vi.hoisted (the hoisting trap, solved)

    import { beforeEach, expect, it, vi } from 'vitest';
    import { getInvoice } from './invoice-service';
    
    // Factory is hoisted above imports — capture handles via vi.hoisted
    const { fetchMock } = vi.hoisted(() => ({ fetchMock: vi.fn() }));
    
    vi.mock('./billing-client', () => ({
      fetchInvoice: fetchMock,
    }));
    
    beforeEach(() => {
      fetchMock.mockReset();
    });
    
    it('retries once on a 503 from the billing client', async () => {
      fetchMock
        .mockRejectedValueOnce(new Error('503 Service Unavailable'))
        .mockResolvedValueOnce({ id: 'inv_42', total: 1999 });
    
      const invoice = await getInvoice('inv_42');
    
      expect(invoice.total).toBe(1999);
      expect(fetchMock).toHaveBeenCalledTimes(2);
    });
    

    Partial mocks keep the rest of a module real:

    vi.mock('./config', async (importOriginal) => {
      const actual = await importOriginal<typeof import('./config')>();
      return { ...actual, isFeatureEnabled: vi.fn().mockReturnValue(true) };
    });
    

    Fake Timers

    import { afterEach, beforeEach, expect, it, vi } from 'vitest';
    import { debounce } from './debounce';
    
    beforeEach(() => vi.useFakeTimers());
    afterEach(() => vi.useRealTimers());
    
    it('fires once after the trailing edge of 300ms', () => {
      const fn = vi.fn();
      const debounced = debounce(fn, 300);
    
      debounced();
      debounced();
      vi.advanceTimersByTime(299);
      expect(fn).not.toHaveBeenCalled();
    
      vi.advanceTimersByTime(1);
      expect(fn).toHaveBeenCalledTimes(1);
    });
    

    Snapshots and Error Assertions

    import { expect, it } from 'vitest';
    import { formatReport, parseDuration } from './report';
    
    it('formats a compact summary line', () => {
      expect(formatReport({ passed: 12, failed: 1, skipped: 2 })).toMatchInlineSnapshot(
        `"12 passed | 1 failed | 2 skipped"`,
      );
    });
    
    it('throws a typed error on malformed durations', () => {
      expect(() => parseDuration('5parsecs')).toThrowErrorMatchingInlineSnapshot(
        `[RangeError: unknown duration unit "parsecs"]`,
      );
    });
    

    Monorepo Projects and In-Source Tests

    // vitest.config.ts at the monorepo root
    import { defineConfig } from 'vitest/config';
    
    export default defineConfig({
      test: {
        projects: [
          { test: { name: 'shared', root: './packages/shared', environment: 'node' } },
          { test: { name: 'web', root: './packages/web', environment: 'jsdom' } },
        ],
      },
    });
    
    vitest run --project shared   # one package
    vitest run                    # everything, parallelized
    

    In-source tests for small internal utilities (stripped from production builds by define: { 'import.meta.vitest': 'undefined' }):

    // src/slug.ts
    export function slugify(input: string): string {
      return input.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
    }
    
    if (import.meta.vitest) {
      const { expect, it } = import.meta.vitest;
      it('collapses punctuation runs into single hyphens', () => {
        expect(slugify('  Hello, World! ')).toBe('hello-world');
      });
    }
    

    Best Practices

    • Set restoreMocks: true globally instead of sprinkling vi.restoreAllMocks() in every afterEach.
    • Use vitest related src/pricing.ts in pre-commit hooks to run only tests touching changed files.
    • Assert promise rejections with await expect(p).rejects.toThrow(...) — a bare expect(p).rejects without await can pass before settlement.
    • Pin the environment per file when only some tests need DOM: // @vitest-environment jsdom at the top of the file.
    • Prefer test.each for input tables over copy-pasted tests; each row reports as its own case.
    • When migrating from Jest: vi replaces jest, vi.mock factories must return the module shape explicitly (no automock), and jest.requireActual becomes importOriginal.

    Anti-Patterns

    • Referencing top-level variables inside a vi.mock factory. Hoisting makes them undefined at factory time — the error message mentions hoisting, believe it. Use vi.hoisted.
    • globals: true plus missing TS types. If you enable globals, add "types": ["vitest/globals"] to tsconfig, or imports break silently in editors.
    • Giant .toMatchSnapshot() on full API responses. Hundred-line snapshots get rubber-stamp updated. Snapshot small, stable slices; assert dynamic fields with matchers.
    • vi.mock of the module under test. You end up testing your own mock. Mock dependencies, never the subject.
    • Forgetting vi.useRealTimers() cleanup — fake timers leak into later tests and hang anything that genuinely waits.
    • Re-implementing Vite aliases inside test.alias when they already exist in vite.config.ts; drift between the two breaks resolution in tests only.

    When to Trigger This Skill

    • The project is Vite-based or has vitest in devDependencies.
    • The user asks to add unit tests, mock a module, fake timers, or snapshot output in a TS/JS repo without Jest.
    • Setting up coverage gates or monorepo test projects with package-specific environments.
    • Migrating a Jest suite to Vitest (jest → vi API mapping, mock factory differences).
    • Tests fail with hoisting errors, environment mismatches, or leaking mocks — the classic Vitest misconfigurations.

    Frequently asked questions

    What to verify before installation and use

    What does the Vitest Testing source document cover?

    This skill makes an AI agent write and configure Vitest test suites: a correct vitest.config.ts, module mocking with vi.mock and the vi.hoisted escape hatch, spies and fake timers, inline snapshots, V8 coverage gates, and projects config for monorepos. Trigger it on any Vite-bas…

    How do I install Vitest Testing?

    The source record exposes this install command: npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/vitest-testing". Inspect the command and pinned source before running it.

    Which Agent platforms does the source record declare?

    The pinned source record declares support for: codex, claude code, cursor, gemini cli.

    Which permission-related actions were detected?

    Static rules flagged exec-script in the source; the page lists the matching lines and excerpts.

    Alternatives

    Compare before choosing