TDD workflow, comprehensive test strategies, test coverage, and quality assurance patterns
/plugin marketplace add Primadetaautomation/claude-dev-toolkit/plugin install claude-dev-toolkit@primadata-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill provides comprehensive testing strategies following TDD principles with 80%+ coverage requirements. It ensures quality assurance through systematic testing approaches.
Step 1: Red - Write Failing Test First
// Write the test BEFORE implementation
describe('UserService', () => {
it('should create a new user with valid data', async () => {
// Arrange
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
// Act
const result = await userService.createUser(userData);
// Assert
expect(result).toHaveProperty('id');
expect(result.email).toBe(userData.email);
expect(result.name).toBe(userData.name);
expect(result).not.toHaveProperty('password'); // Password should not be returned
});
});
// Test should FAIL initially (Red)
Step 2: Green - Write Minimal Code to Pass
class UserService {
async createUser(data: CreateUserDto): Promise<User> {
// Minimal implementation to make test pass
const user = {
id: generateId(),
email: data.email,
name: data.name,
// password is hashed and not returned
};
await this.userRepository.save(user);
return user;
}
}
// Test should PASS now (Green)
Step 3: Refactor - Improve Code Quality
class UserService {
async createUser(data: CreateUserDto): Promise<User> {
// Refactor with better structure
this.validateUserData(data);
const hashedPassword = await this.hashPassword(data.password);
const user = User.create({
email: data.email,
name: data.name,
passwordHash: hashedPassword,
});
return this.userRepository.save(user);
}
private validateUserData(data: CreateUserDto): void {
if (!validator.isEmail(data.email)) {
throw new ValidationError('Invalid email');
}
if (data.password.length < 8) {
throw new ValidationError('Password too short');
}
}
private async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12);
}
}
// Tests still PASS after refactoring
Minimum Standards:
What to Test:
describe('OrderService', () => {
it('should calculate total with tax correctly', () => {
// ===== ARRANGE =====
// Set up test data and dependencies
const mockTaxService = {
getTaxRate: jest.fn().mockResolvedValue(0.21)
};
const orderService = new OrderService(mockTaxService);
const items = [
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 }
];
// ===== ACT =====
// Execute the function being tested
const result = await orderService.calculateTotal(items);
// ===== ASSERT =====
// Verify the results
expect(result.subtotal).toBe(250);
expect(result.tax).toBe(52.5);
expect(result.total).toBe(302.5);
expect(mockTaxService.getTaxRate).toHaveBeenCalledTimes(1);
});
});
// Test single function/method in isolation
describe('calculateDiscount', () => {
it('should apply 10% discount for orders over $100', () => {
expect(calculateDiscount(150)).toBe(15);
});
it('should return 0 for orders under $100', () => {
expect(calculateDiscount(50)).toBe(0);
});
});
When to use:
// Test multiple components together
describe('User Registration Integration', () => {
it('should register user and send welcome email', async () => {
// Uses real UserService, UserRepository, and EmailService
const result = await userService.register({
email: 'test@example.com',
password: 'Test123!'
});
expect(result.user).toBeDefined();
// Verify email was sent
const emails = await testEmailService.getSentEmails();
expect(emails).toHaveLength(1);
expect(emails[0].to).toBe('test@example.com');
});
});
When to use:
// Test complete user flows (Playwright/Cypress)
test('user can complete checkout process', async ({ page }) => {
// Navigate to site
await page.goto('https://example.com');
// Add items to cart
await page.click('[data-testid="add-to-cart"]');
// Go to checkout
await page.click('[data-testid="checkout-button"]');
// Fill shipping info
await page.fill('[name="address"]', '123 Test St');
await page.fill('[name="city"]', 'Amsterdam');
// Complete payment
await page.click('[data-testid="pay-now"]');
// Verify success
await expect(page.locator('.success-message')).toBeVisible();
});
When to use:
// Test data factory for consistent test data
class UserFactory {
static create(overrides: Partial<User> = {}): User {
return {
id: faker.string.uuid(),
email: faker.internet.email(),
name: faker.person.fullName(),
createdAt: new Date(),
...overrides
};
}
static createMany(count: number): User[] {
return Array.from({ length: count }, () => this.create());
}
static createAdmin(): User {
return this.create({
role: 'admin',
permissions: ['read', 'write', 'delete']
});
}
}
// Usage in tests
describe('UserService', () => {
it('should handle multiple users', () => {
const users = UserFactory.createMany(5);
expect(users).toHaveLength(5);
});
it('should allow admin to delete users', () => {
const admin = UserFactory.createAdmin();
expect(admin.permissions).toContain('delete');
});
});
describe('PaymentService', () => {
let paymentService: PaymentService;
let mockStripeClient: jest.Mocked<StripeClient>;
let mockDatabase: jest.Mocked<Database>;
beforeEach(() => {
// Create mocks
mockStripeClient = {
createCharge: jest.fn(),
refund: jest.fn(),
} as any;
mockDatabase = {
saveTransaction: jest.fn(),
} as any;
// Inject mocks
paymentService = new PaymentService(mockStripeClient, mockDatabase);
});
it('should process payment and save transaction', async () => {
// Arrange
mockStripeClient.createCharge.mockResolvedValue({ id: 'ch_123', status: 'succeeded' });
mockDatabase.saveTransaction.mockResolvedValue(true);
// Act
await paymentService.processPayment(100, 'usd');
// Assert
expect(mockStripeClient.createCharge).toHaveBeenCalledWith({
amount: 100,
currency: 'usd'
});
expect(mockDatabase.saveTransaction).toHaveBeenCalled();
});
});
describe('UserService', () => {
describe('createUser', () => {
it('should create user with valid data', () => { /* ... */ });
it('should throw error with invalid email', () => { /* ... */ });
it('should throw error with weak password', () => { /* ... */ });
});
describe('updateUser', () => {
it('should update user fields', () => { /* ... */ });
it('should not update immutable fields', () => { /* ... */ });
});
describe('deleteUser', () => {
it('should soft delete user', () => { /* ... */ });
it('should throw error if user not found', () => { /* ... */ });
});
});
Before marking feature complete:
it('should call getUserById method', () => {
const spy = jest.spyOn(userService, 'getUserById');
userService.getUser('123');
expect(spy).toHaveBeenCalled(); // Testing HOW, not WHAT
});
it('should return user data when valid ID provided', () => {
const user = userService.getUser('123');
expect(user).toBeDefined();
expect(user.id).toBe('123'); // Testing WHAT happens
});
it('should work correctly', () => {
expect(userService.create(data)).toBeDefined();
expect(userService.delete('123')).toBe(true);
expect(userService.list()).toHaveLength(5);
// Too many unrelated assertions
});
it('should create user successfully', () => {
expect(userService.create(data)).toBeDefined();
});
it('should delete user when valid ID provided', () => {
expect(userService.delete('123')).toBe(true);
});
it('should list all users', () => {
expect(userService.list()).toHaveLength(5);
});
See companion files:
advanced-mocking.md - Complex mocking scenariosperformance-testing.md - Load and stress testingsnapshot-testing.md - Visual and data snapshot strategiesSee templates directory:
templates/unit-test.template.tstemplates/integration-test.template.tstemplates/e2e-test.template.tsqa-testing-engineer:
senior-fullstack-developer:
playwright-test-agent:
Version 1.0.0 | TDD Compliant | 80%+ Coverage Required
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 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 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.