From conductor
Provides strategies for writing maintainable unit, integration, and E2E tests using AAA pattern, mocking, test naming, and organization. Useful for TDD and test infrastructure.
npx claudepluginhub rbarcante/claude-conductor --plugin conductorThis skill uses the workspace's default tool permissions.
Guidance for writing effective, maintainable tests. Covers unit testing patterns, integration testing, mocking strategies, and test organization.
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
Guidance 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();