skills/vitest-unit-testing/SKILL.md
Write and maintain Vitest unit tests for TypeScript code. Use when the user needs unit coverage for utilities, services, or stores, or asks for Vitest-based tests with mocks, spies, and assertions.
npx skillsauth add perdolique/workflow vitest-unit-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill provides patterns and conventions for writing comprehensive unit tests in TypeScript projects using Vitest.
__tests__/ directory next to the file being tested.test.ts suffix matching the source file nameconfig.ts → __tests__/config.test.tsimport { describe, test, expect, vi, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
import { functionToTest } from '../module';
describe('Module name or function group', () => {
describe(functionName, () => {
test('should do something specific', () => {
// Arrange
const input = 'test';
// Act
const result = functionToTest(input);
// Assert
expect(result).toBe('expected');
});
});
});
Always structure tests with clear AAA sections:
test('should calculate total price correctly', () => {
// Arrange
const items = [
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 }
];
// Act
const total = calculateTotal(items);
// Assert
expect(total).toBe(250);
});
Use test.each for testing multiple scenarios:
describe(getBaseUrl, () => {
test.each([
['production', 'prod', 'https://app.example.com'],
['staging', 'staging', 'https://staging.example.com'],
['development', 'dev', 'http://localhost:3000']
])('returns correct URL for environment %s', (_name, env, expected) => {
vi.spyOn(envUtils, 'getEnvironment').mockReturnValue(env);
const url = getBaseUrl();
expect(url).toBe(expected);
});
});
describe('Test suite', () => {
beforeAll(() => {
// Runs once before all tests
});
beforeEach(() => {
// Runs before each test
});
afterEach(() => {
// Runs after each test — restore mocks here
vi.restoreAllMocks();
});
afterAll(() => {
// Runs once after all tests
vi.useRealTimers();
});
});
See references/mocking.md for the comprehensive mocking guide.
// Mock entire module
vi.mock(import('./utils/logging'), () => ({
logException: vi.fn()
}));
// Spy on function
vi.spyOn(module, 'functionName').mockReturnValue('result');
// Spy on getter
vi.spyOn(window.location, 'hostname', 'get').mockReturnValue('test.com');
// Fake timers
vi.useFakeTimers();
vi.setSystemTime(new Date('2024-01-01'));
vi.advanceTimersByTimeAsync(1000); // Use async version for promise-based timers
// Cleanup
vi.restoreAllMocks();
vi.useRealTimers();
Use descriptive test names explaining expected behavior. Start with "should" or describe the outcome. Be specific about the scenario being tested.
// Good
test('should return empty array when no items match filter', () => {});
test('throws error when user ID is invalid', () => {});
// Bad
test('works correctly', () => {});
test('test filter', () => {});
Use nested describe blocks for organization:
describe('UserService', () => {
describe(getUser, () => {
test('should fetch user by ID', () => {});
test('should handle missing user', () => {});
});
describe(updateUser, () => {
test('should update user data', () => {});
test('should validate input before update', () => {});
});
});
Always test:
afterEach or afterAllvi.hoisted() for shared mocks referenced in module mocksPrefer specific matchers:
// Good — specific matchers
expect(result).toBe(true);
expect(array).toHaveLength(3);
expect(object).toStrictEqual({ id: 1, name: 'test' });
expect(fn).toHaveBeenCalledWith('expected-arg');
expect(fn).toHaveBeenCalledTimes(1);
// Less specific but sometimes necessary
expect(result).toBeTruthy();
expect(result).toBeFalsy();
Always use the runTests tool instead of running tests manually in terminal. The tool automatically runs tests in non-interactive mode and provides structured output for validation.
If you must use terminal commands, use the CI/non-interactive mode of the project's test script (e.g., vitest run or the project's equivalent). Never use a command that starts interactive watch mode.
# ❌ NEVER use watch mode
npx vitest
# ✅ Use run mode for specific test files
npx vitest run src/utils/__tests__/config.test.ts
# Run with coverage
npx vitest run --coverage
# Run all tests in CI mode
npx vitest run
Check the project's package.json for available test scripts — many projects define shortcuts like test:unit, test:unit:ci, or similar.
afterEach or afterAll with vi.restoreAllMocks()expect()vi.useFakeTimers()// @ts-expect-error when intentionally passing invalid types to test error handlingvi.mocked() for type-safe access to mocked functionssatisfies for type-checked assertion payloads without losing literal typesdevelopment
Vue 3 + TypeScript component conventions for `.vue` SFC work. Use for Vue UI tasks that change component APIs/templates/styling/accessibility/composables/template refs/v-model or related component behavior. For Nuxt/Pinia/routing/E2E/Vitest tasks apply only to component-layer code and combine with the more specific local skill.
tools
Create or draft GitHub releases from existing tags and repository history. Use this whenever the user asks to publish a GitHub release, create release notes for a new version, mirror previous GitHub releases, release a tag/version, or says they have already released a new package version and need the matching GitHub release.
development
Plan and drive non-trivial coding work from ambiguous request to scoped implementation and verification. Use when the user asks to plan before coding, plan then implement, split work into iterations or PR-sized tasks, tackle a risky multi-file feature, refactor, migration, or recover after failed work. Do not use for simple one-step edits, commit or PR creation, pure framework/domain conventions, or repo-specific roadmap docs where a more specific planning skill applies.
development
TypeScript coding conventions for writing, reviewing, and refactoring typed code. Use when working on `.ts`, `.tsx`, or files that embed TypeScript such as Vue, Astro, or Svelte components. Also use for TypeScript snippets, typed refactors, and review comments about code organization or function structure.