skills/emz1998/tdd-vitest-typescript/SKILL.md
Test-Driven Development (TDD) using Vitest and TypeScript. Use when the user requests help with TDD, writing tests before code, test-first development, Vitest test setup, TypeScript testing patterns, unit testing, integration testing, or following the Red-Green-Refactor cycle with Vitest.
npx skillsauth add aiskillstore/marketplace tdd-vitest-typescriptInstall 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.
Guide Claude through Test-Driven Development workflows using Vitest and TypeScript.
Always follow this three-phase cycle:
// 1. RED: Write the test first
describe('Calculator', () => {
it('adds two numbers', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});
});
// Run test → Watch it fail (Red)
// 2. GREEN: Implement minimal code
class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
// Run test → Watch it pass (Green)
// 3. REFACTOR: Improve if needed while keeping tests green
Create vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node', // or 'jsdom' for DOM testing
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
});
Ensure tsconfig.json includes:
{
"compilerOptions": {
"types": ["vitest/globals"],
"esModuleInterop": true,
"skipLibCheck": true
}
}
*.test.ts or *.spec.ts__tests__ directoriescalculator.ts → calculator.test.tsimport { describe, it, expect, beforeEach, afterEach } from 'vitest';
describe('FeatureName', () => {
// Setup
beforeEach(() => {
// Runs before each test
});
afterEach(() => {
// Cleanup after each test
});
describe('specific behavior', () => {
it('does something specific', () => {
// Arrange
const input = setupTestData();
// Act
const result = performAction(input);
// Assert
expect(result).toBe(expected);
});
});
});
interface User {
id: number;
name: string;
email: string;
}
function createTestUser(overrides?: Partial<User>): User {
return {
id: 1,
name: 'Test User',
email: '[email protected]',
...overrides,
};
}
it('processes user data', () => {
const user = createTestUser({ name: 'Custom Name' });
expect(processUser(user)).toBeDefined();
});
describe('generic array utilities', () => {
it('filters array by predicate', () => {
const numbers = [1, 2, 3, 4, 5];
const result = filter(numbers, (n: number) => n > 3);
expect(result).toEqual([4, 5]);
});
});
describe('async operations', () => {
it('fetches user data', async () => {
const user = await fetchUser(1);
expect(user.id).toBe(1);
});
it('handles errors', async () => {
await expect(fetchUser(-1)).rejects.toThrow('Invalid ID');
});
});
import { vi } from 'vitest';
import { fetchData } from './api';
vi.mock('./api');
describe('data processing', () => {
it('processes fetched data', async () => {
vi.mocked(fetchData).mockResolvedValue({ data: 'test' });
const result = await processData();
expect(result).toBe('processed: test');
});
});
describe('event handling', () => {
it('calls callback on event', () => {
const callback = vi.fn();
const handler = new EventHandler(callback);
handler.trigger('test-event');
expect(callback).toHaveBeenCalledWith('test-event');
expect(callback).toHaveBeenCalledTimes(1);
});
});
import * as utils from './utils';
vi.spyOn(utils, 'helperFunction').mockReturnValue('mocked');
it('uses mocked helper', () => {
const result = mainFunction();
expect(utils.helperFunction).toHaveBeenCalled();
expect(result).toContain('mocked');
});
describe('UserService', () => {
let service: UserService;
let mockRepository: MockRepository;
beforeEach(() => {
mockRepository = new MockRepository();
service = new UserService(mockRepository);
});
it('creates user with valid data', async () => {
const userData = { name: 'John', email: '[email protected]' };
const user = await service.createUser(userData);
expect(user.id).toBeDefined();
expect(mockRepository.save).toHaveBeenCalledWith(
expect.objectContaining(userData)
);
});
});
describe('pure utility functions', () => {
it('capitalizes first letter', () => {
expect(capitalize('hello')).toBe('Hello');
expect(capitalize('')).toBe('');
expect(capitalize('WORLD')).toBe('WORLD');
});
});
describe('error scenarios', () => {
it('throws on invalid input', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
it('returns error result', () => {
const result = parseJSON('invalid json');
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
import { describe, it, expect } from 'vitest';
describe.each([
{ input: 2, expected: 4 },
{ input: 3, expected: 9 },
{ input: 4, expected: 16 },
])('square function', ({ input, expected }) => {
it(`squares ${input} to ${expected}`, () => {
expect(square(input)).toBe(expected);
});
});
vitest --coverage
Always test:
Consider skipping:
Always start with the test, not the implementation:
// ❌ BAD: Writing implementation first
class Calculator {
add(a: number, b: number) { return a + b; }
}
// ✅ GOOD: Test first
it('adds two numbers', () => {
expect(new Calculator().add(2, 3)).toBe(5);
});
Keep tests focused:
// ❌ BAD: Multiple concerns
it('user operations', () => {
const user = createUser();
expect(user.id).toBeDefined();
expect(updateUser(user)).toBeTruthy();
expect(deleteUser(user.id)).toBeUndefined();
});
// ✅ GOOD: Single concern
it('creates user with ID', () => {
const user = createUser();
expect(user.id).toBeDefined();
});
it('updates existing user', () => {
const user = createUser();
expect(updateUser(user)).toBeTruthy();
});
// ❌ BAD: Testing implementation details
it('calls internal helper method', () => {
const service = new Service();
const spy = vi.spyOn(service as any, '_internalHelper');
service.process();
expect(spy).toHaveBeenCalled();
});
// ✅ GOOD: Testing behavior
it('processes data correctly', () => {
const service = new Service();
const result = service.process(inputData);
expect(result).toEqual(expectedOutput);
});
// ❌ BAD: Vague
it('works', () => { /* ... */ });
// ✅ GOOD: Descriptive
it('returns empty array when no users match filter criteria', () => {
/* ... */
});
describe('User Registration', () => {
it('creates new user account with valid email', async () => {
const result = await registerUser({
email: '[email protected]',
password: 'secure123',
});
expect(result.success).toBe(true);
expect(result.user.email).toBe('[email protected]');
});
});
it('rejects registration with existing email', async () => {
await registerUser({ email: '[email protected]', password: 'pass' });
const result = await registerUser({
email: '[email protected]',
password: 'pass2',
});
expect(result.success).toBe(false);
expect(result.error).toContain('Email already registered');
});
it('rejects weak passwords', async () => {
const result = await registerUser({
email: '[email protected]',
password: '123',
});
expect(result.success).toBe(false);
expect(result.error).toContain('Password too weak');
});
When tests fail unexpectedly:
it.only() to run single testdescribe('Order Processing Integration', () => {
let database: TestDatabase;
let paymentGateway: MockPaymentGateway;
let orderService: OrderService;
beforeEach(async () => {
database = await TestDatabase.create();
paymentGateway = new MockPaymentGateway();
orderService = new OrderService(database, paymentGateway);
});
afterEach(async () => {
await database.cleanup();
});
it('completes order flow from cart to confirmation', async () => {
const user = await database.createUser();
const cart = await orderService.createCart(user.id);
await orderService.addItem(cart.id, { productId: 1, quantity: 2 });
paymentGateway.simulateSuccess();
const order = await orderService.checkout(cart.id);
expect(order.status).toBe('confirmed');
expect(order.items).toHaveLength(1);
});
});
Vitest runs in watch mode by default during development:
vitest
This automatically re-runs tests when files change, enabling rapid TDD cycles.
// Equality
expect(value).toBe(5); // Strict equality (===)
expect(value).toEqual({ a: 1 }); // Deep equality
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThan(10);
expect(value).toBeCloseTo(0.3); // Floating point
// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
// Arrays
expect(array).toContain(item);
expect(array).toHaveLength(3);
// Objects
expect(object).toHaveProperty('key');
expect(object).toMatchObject({ a: 1 }); // Partial match
// Exceptions
expect(() => fn()).toThrow();
expect(() => fn()).toThrow('Error message');
expect(async () => fn()).rejects.toThrow();
// Functions
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith(arg1, arg2);
expect(fn).toHaveBeenCalledTimes(2);
Apply TDD when:
TDD is especially valuable for:
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.