Use this skill when writing tests, implementing TDD, or setting up test infrastructure.
Provides guidance for writing effective, maintainable tests with patterns and strategies.
/plugin marketplace add https://www.claudepluginhub.com/api/plugins/rbarcante-conductor/marketplace.json/plugin install rbarcante-conductor@cpd-rbarcante-conductorThis skill inherits all available tools. When active, it can use any tool Claude has access to.
README.mdmanifest.jsonpatterns/integration-patterns.mdpatterns/mocking-strategies.mdpatterns/unit-test-patterns.mdGuidance for writing effective, maintainable tests. Covers unit testing patterns, integration testing, mocking strategies, and test organization.
describe('UserService', () => {
describe('createUser', () => {
it('should create user with valid data', async () => {
// Arrange
const userData = { name: 'John', email: 'john@example.com' };
const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) };
const service = new UserService(mockRepo);
// Act
const result = await service.createUser(userData);
// Assert
expect(result.id).toBe('1');
expect(result.name).toBe('John');
expect(mockRepo.save).toHaveBeenCalledWith(userData);
});
});
});
// Pattern: should [expected behavior] when [condition]
it('should return null when user not found', () => {});
it('should throw ValidationError when email is invalid', () => {});
it('should retry 3 times when connection fails', () => {});
// Or: [condition] => [expected behavior]
it('invalid email => throws ValidationError', () => {});
it('user not found => returns null', () => {});
src/
services/
user.ts
user.test.ts # Co-located unit tests
tests/
integration/
user.integration.ts # Integration tests
e2e/
user-flow.e2e.ts # End-to-end tests
fixtures/
users.ts # Shared test data
helpers/
test-db.ts # Test utilities
// Function to test
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Tests
describe('calculateTotal', () => {
it('should return 0 for empty array', () => {
expect(calculateTotal([])).toBe(0);
});
it('should calculate total for single item', () => {
const items = [{ price: 10, quantity: 2 }];
expect(calculateTotal(items)).toBe(20);
});
it('should calculate total for multiple items', () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 }
];
expect(calculateTotal(items)).toBe(35);
});
});
// Class to test
class OrderService {
constructor(
private orderRepo: OrderRepository,
private paymentGateway: PaymentGateway
) {}
async placeOrder(order: Order): Promise<OrderResult> {
const savedOrder = await this.orderRepo.save(order);
const payment = await this.paymentGateway.charge(order.total);
return { order: savedOrder, payment };
}
}
// Test with mocks
describe('OrderService', () => {
let service: OrderService;
let mockOrderRepo: jest.Mocked<OrderRepository>;
let mockPaymentGateway: jest.Mocked<PaymentGateway>;
beforeEach(() => {
mockOrderRepo = { save: jest.fn() };
mockPaymentGateway = { charge: jest.fn() };
service = new OrderService(mockOrderRepo, mockPaymentGateway);
});
it('should save order and charge payment', async () => {
// Arrange
const order = { id: '1', total: 100 };
mockOrderRepo.save.mockResolvedValue(order);
mockPaymentGateway.charge.mockResolvedValue({ success: true });
// Act
const result = await service.placeOrder(order);
// Assert
expect(mockOrderRepo.save).toHaveBeenCalledWith(order);
expect(mockPaymentGateway.charge).toHaveBeenCalledWith(100);
expect(result.order).toEqual(order);
});
});
Mock:
Don't Mock:
// Stub - returns canned response
const userRepo = { findById: jest.fn().mockResolvedValue(mockUser) };
// Spy - tracks calls while using real implementation
const spy = jest.spyOn(console, 'log');
// ... run code
expect(spy).toHaveBeenCalledWith('message');
// Fake - simplified working implementation
class FakeUserRepo implements UserRepository {
private users: Map<string, User> = new Map();
async save(user: User) {
this.users.set(user.id, user);
return user;
}
async findById(id: string) {
return this.users.get(id);
}
}
// Mock module
jest.mock('./database', () => ({
query: jest.fn()
}));
// Mock specific method
jest.spyOn(Date, 'now').mockReturnValue(1234567890);
// Mock implementation
mockFn.mockImplementation((x) => x * 2);
// Mock once (for specific test)
mockFn.mockResolvedValueOnce(firstResult);
mockFn.mockResolvedValueOnce(secondResult);
// Reset mocks between tests
beforeEach(() => {
jest.clearAllMocks();
});
describe('UserRepository', () => {
let db: Database;
let repo: UserRepository;
beforeAll(async () => {
db = await createTestDatabase();
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.clear();
repo = new UserRepository(db);
});
it('should persist and retrieve user', async () => {
// Arrange
const user = { name: 'John', email: 'john@example.com' };
// Act
const saved = await repo.create(user);
const found = await repo.findById(saved.id);
// Assert
expect(found).toEqual(saved);
});
});
describe('POST /api/users', () => {
let app: Express;
beforeAll(() => {
app = createApp();
});
it('should create user and return 201', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: 'john@example.com' });
expect(response.status).toBe(201);
expect(response.body.data).toHaveProperty('id');
expect(response.body.data.name).toBe('John');
});
it('should return 422 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: 'invalid' });
expect(response.status).toBe(422);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
});
| Metric | Target | Critical Path |
|---|---|---|
| Line | 80% | 95%+ |
| Branch | 75% | 90%+ |
| Function | 85% | 95%+ |
// Don't write tests just for coverage
// Bad - tests implementation, not behavior
it('should call validateEmail', () => {
service.createUser({ email: 'test@example.com' });
expect(validateEmail).toHaveBeenCalled();
});
// Good - tests actual behavior
it('should reject invalid email format', () => {
expect(() => service.createUser({ email: 'invalid' }))
.toThrow(ValidationError);
});
// Equality
expect(value).toBe(expected); // Strict equality
expect(value).toEqual(expected); // Deep equality
expect(value).toBeNull();
expect(value).toBeDefined();
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThanOrEqual(10);
expect(value).toBeCloseTo(0.3, 5); // Floating point
// Strings
expect(str).toMatch(/pattern/);
expect(str).toContain('substring');
// Arrays
expect(array).toContain(item);
expect(array).toHaveLength(3);
// Objects
expect(obj).toHaveProperty('key');
expect(obj).toMatchObject({ key: 'value' });
// Exceptions
expect(() => fn()).toThrow();
expect(() => fn()).toThrow(ErrorType);
expect(() => fn()).toThrow('message');
// Async
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow();
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.