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…
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
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Declared | Source record | Install path and trigger |
| Claude Code | Declared | Source record | Install path and trigger |
| Cursor | Declared | Source record | Install path and trigger |
| Gemini CLI | Declared | Source record | Install path and trigger |
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.
npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/vitest-testing"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
- 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. - 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. - 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' }): - 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. - 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
The documentation asks the agent to run terminal commands or scripts.
npm install --save-dev vitest @vitest/coverage-v8Evidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 92/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 211 | Source | Repository attention, not individual Skill quality |
| Compatibility | 4 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated 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
- Vitest reuses your Vite config — do not duplicate resolution logic. Aliases, plugins, and transforms from
vite.config.tsapply to tests automatically. A separate Babel/transform setup is a Jest habit; drop it. vi.mockis hoisted; factory variables are not. The mock factory runs before imports, so referencing top-level variables inside it throws. Usevi.hoisted()when the factory needs shared handles.- Prefer
vi.fninjected via parameters overvi.mockof whole modules. Module mocking is a sledgehammer; dependency injection keeps tests honest and refactor-safe. - Inline snapshots over file snapshots for small values.
toMatchInlineSnapshotputs the expectation in the test where reviewers see it; file snapshots get blindly--updated. - Coverage thresholds live in config and fail the run. A coverage report nobody gates on is wallpaper. Gate
lines,functions, andbranches— branch coverage is where the bugs hide. - Use the default
nodeenvironment unless you render DOM.jsdom/happy-domcost 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: trueglobally instead of sprinklingvi.restoreAllMocks()in everyafterEach. - Use
vitest related src/pricing.tsin pre-commit hooks to run only tests touching changed files. - Assert promise rejections with
await expect(p).rejects.toThrow(...)— a bareexpect(p).rejectswithout await can pass before settlement. - Pin the environment per file when only some tests need DOM:
// @vitest-environment jsdomat the top of the file. - Prefer
test.eachfor input tables over copy-pasted tests; each row reports as its own case. - When migrating from Jest:
vireplacesjest,vi.mockfactories must return the module shape explicitly (no automock), andjest.requireActualbecomesimportOriginal.
Anti-Patterns
- Referencing top-level variables inside a
vi.mockfactory. Hoisting makes themundefinedat factory time — the error message mentions hoisting, believe it. Usevi.hoisted. globals: trueplus 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.mockof 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.aliaswhen they already exist invite.config.ts; drift between the two breaks resolution in tests only.
When to Trigger This Skill
- The project is Vite-based or has
vitestindevDependencies. - 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
PramodDutta/qaskills
Pairwise Test Generator
Generate optimized test combinations using pairwise (all-pairs) testing algorithms to achieve maximum coverage with minimum test cases across multiple input parameters
PramodDutta/qaskills
RAG Regression Testing
Gate RAG pipelines in CI with versioned golden eval sets, per-metric thresholds, baseline drift detection, and a build that fails when retrieval or answer quality regresses.
PramodDutta/qaskills
State Machine Test Generator
Generate comprehensive test cases from state machine models covering all states, transitions, guard conditions, and invalid transition attempts for workflow-heavy features
PramodDutta/qaskills
Test Case Generator from User Stories
Automatically generate comprehensive test cases from user stories and acceptance criteria using BDD patterns, equivalence partitioning, and risk-based prioritization