Comprehensive unit testing expertise covering Vitest, Jest, test-driven development (TDD), mocking strategies, test coverage, snapshot testing, test architecture, testing patterns, dependency injection, test doubles (mocks, stubs, spies, fakes), async testing, error handling tests, parametric testing, test organization, code coverage analysis, mutation testing, and production-grade unit testing best practices. Activates for unit testing, vitest, jest, test-driven development, TDD, red-green-refactor, mocking, stubbing, spying, test doubles, test coverage, snapshot testing, test architecture, dependency injection, async testing, test patterns, code coverage, mutation testing, test isolation, test fixtures, AAA pattern, given-when-then, test organization, testing best practices, vi.fn, vi.mock, vi.spyOn, describe, it, expect, beforeEach, afterEach.
Provides expert guidance for unit testing with Vitest and Jest, including TDD, mocking, and best practices. Activates when users mention unit testing, test-driven development, mocking strategies, or need help writing tests.
/plugin marketplace add anton-abyzov/specweave/plugin install sw-testing@specweaveThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Self-contained unit testing expertise for Vitest/Jest in ANY user project.
Red-Green-Refactor Cycle:
// 1. RED: Write failing test
describe('Calculator', () => {
it('should add two numbers', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});
});
// 2. GREEN: Minimal implementation
class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
// 3. REFACTOR: Improve code
class Calculator {
add(...numbers: number[]): number {
return numbers.reduce((sum, n) => sum + n, 0);
}
}
TDD Benefits:
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { UserService } from './UserService';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
afterEach(() => {
vi.clearAllMocks();
});
it('should create user', () => {
const user = service.create({ name: 'John', email: 'john@test.com' });
expect(user).toMatchObject({
id: expect.any(String),
name: 'John',
email: 'john@test.com'
});
});
it('should throw for invalid email', () => {
expect(() => {
service.create({ name: 'John', email: 'invalid' });
}).toThrow('Invalid email');
});
});
it('should fetch user from API', async () => {
const user = await api.fetchUser('user-123');
expect(user).toEqual({
id: 'user-123',
name: 'John Doe'
});
});
// Testing async errors
it('should handle API errors', async () => {
await expect(api.fetchUser('invalid')).rejects.toThrow('User not found');
});
// Mock a function
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);
// Mock with implementation
const mockAdd = vi.fn((a, b) => a + b);
expect(mockAdd(2, 3)).toBe(5);
// Verify calls
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(expected);
// Mock entire module
vi.mock('./database', () => ({
query: vi.fn().mockResolvedValue([{ id: 1, name: 'Test' }])
}));
import { query } from './database';
it('should fetch users from database', async () => {
const users = await query('SELECT * FROM users');
expect(users).toHaveLength(1);
});
// Spy on existing method
const spy = vi.spyOn(console, 'log');
myFunction();
expect(spy).toHaveBeenCalledWith('Expected message');
spy.mockRestore();
class UserService {
constructor(private db: Database) {}
async getUser(id: string) {
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
}
// Test with mock
const mockDb = {
query: vi.fn().mockResolvedValue({ id: '123', name: 'John' })
};
const service = new UserService(mockDb);
const user = await service.getUser('123');
expect(mockDb.query).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = ?',
['123']
);
it('should calculate total price', () => {
// Arrange
const cart = new ShoppingCart();
cart.addItem({ price: 10, quantity: 2 });
cart.addItem({ price: 5, quantity: 3 });
// Act
const total = cart.getTotal();
// Assert
expect(total).toBe(35);
});
describe('Shopping Cart', () => {
it('should apply discount when total exceeds $100', () => {
// Given: A cart with items totaling $120
const cart = new ShoppingCart();
cart.addItem({ price: 120, quantity: 1 });
// When: Getting the total
const total = cart.getTotal();
// Then: 10% discount applied
expect(total).toBe(108); // $120 - $12 (10%)
});
});
describe.each([
[2, 3, 5],
[10, 5, 15],
[-1, 1, 0],
[0, 0, 0]
])('Calculator.add(%i, %i)', (a, b, expected) => {
it(`should return ${expected}`, () => {
const calc = new Calculator();
expect(calc.add(a, b)).toBe(expected);
});
});
Mock: Verifies behavior (calls, arguments)
const mock = vi.fn();
mock('test');
expect(mock).toHaveBeenCalledWith('test');
Stub: Returns predefined values
const stub = vi.fn().mockReturnValue(42);
expect(stub()).toBe(42);
Spy: Observes real function
const spy = vi.spyOn(obj, 'method');
obj.method();
expect(spy).toHaveBeenCalled();
Fake: Working implementation (simplified)
class FakeDatabase {
private data = new Map();
async save(key, value) {
this.data.set(key, value);
}
async get(key) {
return this.data.get(key);
}
}
# Vitest
vitest --coverage
# Jest
jest --coverage
// vitest.config.ts
export default {
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
lines: 80,
functions: 80,
branches: 80,
statements: 80
}
}
};
✅ DO:
❌ DON'T:
Good use cases:
it('should render user card', () => {
const card = renderUserCard({ name: 'John', role: 'Admin' });
expect(card).toMatchSnapshot();
});
// Update snapshots: vitest -u
Avoid snapshots for:
src/
├── services/
│ ├── UserService.ts
│ └── UserService.test.ts ← Co-located
tests/
├── unit/
│ └── utils.test.ts
├── integration/
│ └── api.test.ts
└── fixtures/
└── users.json
✅ GOOD:
describe('UserService.create', () => {
it('should create user with valid email', () => {});
it('should throw error for invalid email', () => {});
it('should generate unique ID', () => {});
});
❌ BAD:
describe('UserService', () => {
it('test1', () => {});
it('should work', () => {});
});
// Synchronous errors
it('should throw for negative numbers', () => {
expect(() => sqrt(-1)).toThrow('Cannot compute square root of negative');
});
// Async errors
it('should reject for invalid ID', async () => {
await expect(fetchUser('invalid')).rejects.toThrow('Invalid ID');
});
// Error types
it('should throw TypeError', () => {
expect(() => doSomething()).toThrow(TypeError);
});
// Custom errors
it('should throw ValidationError', () => {
expect(() => validate()).toThrow(ValidationError);
});
let service: UserService;
beforeEach(() => {
service = new UserService();
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
❌ BAD:
let user;
it('should create user', () => {
user = createUser(); // Shared state
});
it('should update user', () => {
updateUser(user); // Depends on previous test
});
✅ GOOD:
it('should update user', () => {
const user = createUser();
updateUser(user);
expect(user.updated).toBe(true);
});
✅ DO:
❌ DON'T:
expect(value).toBe(expected); // ===
expect(value).toEqual(expected); // Deep equality
expect(value).toBeTruthy(); // Boolean true
expect(value).toBeFalsy(); // Boolean false
expect(array).toHaveLength(3); // Array length
expect(array).toContain(item); // Array includes
expect(string).toMatch(/pattern/); // Regex match
expect(fn).toThrow(Error); // Throws error
expect(obj).toHaveProperty('key'); // Has property
expect(value).toBeCloseTo(0.3, 5); // Float comparison
beforeAll(() => {}); // Once before all tests
beforeEach(() => {}); // Before each test
afterEach(() => {}); // After each test
afterAll(() => {}); // Once after all tests
vi.fn() // Create mock
vi.fn().mockReturnValue(x) // Return value
vi.fn().mockResolvedValue(x) // Async return
vi.fn().mockRejectedValue(e) // Async error
vi.mock('./module') // Mock module
vi.spyOn(obj, 'method') // Spy on method
vi.clearAllMocks() // Clear call history
vi.resetAllMocks() // Reset + clear
vi.restoreAllMocks() // Restore originals
This skill is self-contained and works in ANY user project with Vitest/Jest.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.