From claude-starter-kit
Guides building test suites with testing pyramid, TDD/BDD, mocking strategies, AAA pattern for unit, integration, E2E tests to ensure coverage and refactor safety.
npx claudepluginhub sunnypatneedi/claude-starter-kitThis skill uses the workspace's default tool permissions.
Complete framework for building effective, maintainable test suites that give you confidence to ship.
Designs and implements testing strategies for JS/TS, Python, and Go codebases. Covers unit, integration, E2E tests with framework recommendations (Vitest, Jest, Playwright, pytest), templates, pyramid, coverage, and debugging failures.
Provides language-agnostic test strategy guidelines: test pyramid (unit/integration/E2E), descriptive naming, mocking, flaky test policies. For writing tests, strategy design, coverage review.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Complete framework for building effective, maintainable test suites that give you confidence to ship.
Test Behavior, Not Implementation:
Testing Pyramid:
/\
/ \ E2E (Few, slow, comprehensive)
/----\
/ \ Integration (Some, medium)
/--------\
/ \ Unit (Many, fast, isolated)
/------------\
Write Tests That Fail Usefully:
Unit Tests (70% of tests):
WHEN TO USE:
- Business logic
- Data transformations
- Validation rules
- Pure functions
- Edge cases
CHARACTERISTICS:
- Fast (<100ms each)
- Isolated (no external dependencies)
- Many tests
- Run on every commit
Integration Tests (20% of tests):
WHEN TO USE:
- API endpoints
- Database operations
- External service integration
- Multiple components working together
CHARACTERISTICS:
- Medium speed (100ms-1s each)
- Real dependencies (test DB, APIs)
- Fewer than unit tests
- Run before merge
E2E Tests (10% of tests):
WHEN TO USE:
- Critical user journeys
- Complete workflows
- Cross-browser compatibility
CHARACTERISTICS:
- Slow (seconds per test)
- Full application stack
- Fewest tests
- Run before release
Arrange-Act-Assert:
describe('UserService', () => {
describe('createUser', () => {
it('creates a user with valid data', async () => {
// ARRANGE: Set up test data and dependencies
const userData = {
email: 'test@example.com',
name: 'Test User'
};
const mockRepo = {
save: jest.fn().mockResolvedValue({ id: '1', ...userData })
};
const service = new UserService(mockRepo);
// ACT: Execute the code under test
const result = await service.createUser(userData);
// ASSERT: Verify the outcome
expect(result).toEqual({ id: '1', ...userData });
expect(mockRepo.save).toHaveBeenCalledWith(userData);
});
it('throws validation error for invalid email', async () => {
// ARRANGE
const userData = { email: 'invalid', name: 'Test' };
const service = new UserService(mockRepo);
// ACT & ASSERT
await expect(service.createUser(userData))
.rejects.toThrow('Invalid email format');
});
});
});
What to Unit Test:
✅ DO TEST:
- Business logic
- Data transformations
- Validation rules
- Edge cases (null, empty, max values)
- Error conditions
- Pure functions
❌ DON'T TEST:
- Third-party libraries
- Simple getters/setters
- Framework code
- Configuration
- Trivial code
Mocking Guidelines:
// ✅ GOOD: Mock external dependencies
const mockEmailService = {
send: jest.fn().mockResolvedValue(true)
};
const mockDatabase = {
query: jest.fn().mockResolvedValue([{ id: 1 }])
};
// ✅ GOOD: Use dependency injection
class UserService {
constructor(private db: Database, private email: EmailService) {}
async createUser(data) {
const user = await this.db.save(data);
await this.email.send(user.email, 'Welcome!');
return user;
}
}
// In test:
const service = new UserService(mockDb, mockEmail);
// ❌ BAD: Mocking internal implementation details
// This couples tests to implementation
API Integration Tests:
describe('POST /api/users', () => {
let db: Database;
beforeAll(async () => {
db = await createTestDatabase();
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.query('DELETE FROM users');
});
it('creates a user and returns 201', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'test@example.com', name: 'Test' })
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
email: 'test@example.com'
});
// Verify in database
const user = await db.users.findOne({ email: 'test@example.com' });
expect(user).toBeDefined();
});
it('returns 400 for invalid data', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'invalid' })
.expect(400);
expect(response.body.error).toBe('Invalid email format');
});
it('returns 409 for duplicate email', async () => {
await createUser({ email: 'test@example.com' });
await request(app)
.post('/api/users')
.send({ email: 'test@example.com', name: 'Test' })
.expect(409);
});
});
Page Object:
// pages/LoginPage.ts
class LoginPage {
constructor(private page: Page) {}
async navigate() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.page.fill('[data-testid="email"]', email);
await this.page.fill('[data-testid="password"]', password);
await this.page.click('[data-testid="submit"]');
}
async getErrorMessage() {
return this.page.textContent('[data-testid="error"]');
}
}
// tests/login.spec.ts
describe('Login', () => {
it('logs in successfully with valid credentials', async () => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('user@example.com', 'password');
await expect(page).toHaveURL('/dashboard');
});
it('shows error for invalid credentials', async () => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('user@example.com', 'wrong');
const error = await loginPage.getErrorMessage();
expect(error).toBe('Invalid credentials');
});
});
E2E Best Practices:
✅ DO:
- Use data-testid attributes
- Test user journeys, not features
- Run in realistic environment
- Handle async properly
- Clean up test data
❌ DON'T:
- Test every edge case with E2E
- Use brittle selectors (.class-name-xyz)
- Share state between tests
- Skip cleanup
- Run E2E on every commit (too slow)
Red-Green-Refactor Cycle:
1. RED: Write a failing test
└── Test describes desired behavior
2. GREEN: Write minimal code to pass
└── Just enough to make test pass
3. REFACTOR: Clean up
└── Improve code while tests pass
TDD Example:
// Step 1: RED - Write failing test
describe('Cart', () => {
it('calculates total price with discount', () => {
const cart = new Cart();
cart.addItem({ price: 100, quantity: 2 });
cart.applyDiscount(10); // 10%
expect(cart.total).toBe(180); // (100 * 2) - 10%
});
});
// Run test: FAILS (Cart doesn't exist yet)
// Step 2: GREEN - Minimal implementation
class Cart {
items = [];
discount = 0;
addItem(item) {
this.items.push(item);
}
applyDiscount(percent) {
this.discount = percent;
}
get total() {
const subtotal = this.items.reduce(
(sum, i) => sum + i.price * i.quantity,
0
);
return subtotal * (1 - this.discount / 100);
}
}
// Run test: PASSES
// Step 3: REFACTOR - Clean up
class Cart {
private items: CartItem[] = [];
private discountPercent: number = 0;
addItem(item: CartItem): void {
this.items.push(item);
}
applyDiscount(percent: number): void {
this.discountPercent = percent;
}
get total(): number {
const subtotal = this.calculateSubtotal();
return this.applyDiscountToSubtotal(subtotal);
}
private calculateSubtotal(): number {
return this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
}
private applyDiscountToSubtotal(subtotal: number): number {
return subtotal * (1 - this.discountPercent / 100);
}
}
// Run test: STILL PASSES (refactoring didn't break anything)
Coverage Metrics:
LINE COVERAGE: % of lines executed
├── Easy to game
├── Doesn't ensure quality
BRANCH COVERAGE: % of decision branches
├── Better than line coverage
├── Catches missing else cases
MUTATION TESTING: % of mutations caught
├── Introduces bugs, checks if tests catch them
├── High quality metric
├── Slow to run
Coverage Guidelines:
## Coverage Targets
| Type | Target | Priority |
|------|--------|----------|
| Unit | 80% | Business logic |
| Integration | 60% | API endpoints |
| E2E | Key flows | User journeys |
### What Must Be Tested
- [ ] All public API functions
- [ ] All error handling paths
- [ ] Business rule edge cases
- [ ] Security-sensitive code
### What Can Be Skipped
- Configuration files
- Generated code
- Simple getters/setters
- Third-party wrappers
File Structure:
src/
├── users/
│ ├── user.service.ts
│ ├── user.service.test.ts # Unit tests
│ ├── user.repository.ts
│ └── user.repository.test.ts
├── __tests__/
│ └── integration/
│ └── user.api.test.ts # Integration tests
tests/
├── e2e/
│ ├── login.spec.ts # E2E tests
│ └── checkout.spec.ts
├── fixtures/
│ └── users.json # Test data
└── setup.ts # Test configuration
Naming Conventions:
// Describe the unit being tested
describe('UserService', () => {
// Describe the method
describe('createUser', () => {
// Describe the scenario
describe('when email is valid', () => {
// State what it does
it('creates a new user', () => {});
it('sends welcome email', () => {});
});
describe('when email already exists', () => {
it('throws DuplicateEmailError', () => {});
});
});
});
## Test Review Checklist
### Structure
- [ ] Tests follow AAA pattern
- [ ] One assertion concept per test
- [ ] Descriptive test names
- [ ] Tests are independent
- [ ] No test interdependence
### Coverage
- [ ] Happy path tested
- [ ] Error cases tested
- [ ] Edge cases tested
- [ ] Boundary conditions tested
### Maintainability
- [ ] No flaky tests
- [ ] Setup/teardown is minimal
- [ ] Mocks are at boundaries
- [ ] Tests survive refactoring
### Performance
- [ ] Unit tests <100ms each
- [ ] Integration tests reasonable
- [ ] E2E tests focused
- [ ] Parallelization enabled
| Don't | Do |
|---|---|
| Test implementation details | Test public behavior |
| One assertion per test (too rigid) | One concept per test |
| Copy-paste tests | Use test helpers/factories |
| Share state between tests | Isolate each test |
| Skip edge cases | Test boundaries and errors |
| Mock everything | Mock at boundaries only |
| Ignore flaky tests | Fix or delete flaky tests |
| Write tests after code (always) | Use TDD when beneficial |
JavaScript/TypeScript:
Python:
Ruby:
Go:
/code-review - Reviewing test quality/devops-cicd - Running tests in CI/CD/debugging - Using tests to find bugsLast Updated: 2026-01-22