Complete Test-Driven Development workflow with red-green-refactor cycle
Use this skill when implementing features using Test-Driven Development methodology.
┌──────────────────────────────────────────────────────────┐
│ │
│ 🔴 RED → 🟢 GREEN → 🔵 REFACTOR │
│ Write test Make pass Improve code │
│ that fails (minimal) (keep green) │
│ │
│ ↺ Repeat │
└──────────────────────────────────────────────────────────┘
Understand the requirement
Write the simplest test case first
describe('calculateDiscount', () => {
it('should return 0 for orders under $50', () => {
expect(calculateDiscount(30)).toBe(0);
});
});
Run the test - it MUST fail
npm test -- calculateDiscount
Write the minimal code to pass
function calculateDiscount(orderTotal: number): number {
return 0;
}
Run the test - it MUST pass
npm test -- calculateDiscount
Don't over-engineer - Only write enough code to pass the current test
Add the next test case and repeat the cycle:
it('should return 10% for orders $50-$100', () => {
expect(calculateDiscount(75)).toBe(7.5);
});
it('should apply discount to premium users', () => {
// Arrange
const user = createUser({ isPremium: true });
const order = createOrder({ total: 100 });
// Act
const result = applyDiscount(user, order);
// Assert
expect(result.total).toBe(90);
expect(result.discountApplied).toBe(10);
});
// Format: should_[expected behavior]_when_[condition]
it('should return empty array when no items match filter', () => {});
it('should throw ValidationError when email is invalid', () => {});
it('should retry 3 times when API returns 503', () => {});
Create a password validator that checks:
// Step 1: RED - First test
describe('validatePassword', () => {
it('should reject passwords shorter than 8 characters', () => {
expect(validatePassword('Short1')).toEqual({
valid: false,
errors: ['Password must be at least 8 characters'],
});
});
});
// Step 2: GREEN - Minimal implementation
function validatePassword(password: string) {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
return { valid: errors.length === 0, errors };
}
// Step 3: RED - Next test
it('should reject passwords without uppercase', () => {
expect(validatePassword('lowercase1')).toEqual({
valid: false,
errors: ['Password must contain an uppercase letter'],
});
});
// Step 4: GREEN - Add uppercase check
function validatePassword(password: string) {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain an uppercase letter');
}
return { valid: errors.length === 0, errors };
}
// Step 5: RED - Test for number requirement
it('should reject passwords without numbers', () => {
expect(validatePassword('NoNumbers')).toEqual({
valid: false,
errors: ['Password must contain a number'],
});
});
// Step 6: GREEN - Add number check
function validatePassword(password: string) {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain an uppercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain a number');
}
return { valid: errors.length === 0, errors };
}
// Step 7: REFACTOR - Clean up
const PASSWORD_RULES = [
{ test: (p: string) => p.length >= 8, message: 'Password must be at least 8 characters' },
{ test: (p: string) => /[A-Z]/.test(p), message: 'Password must contain an uppercase letter' },
{ test: (p: string) => /[0-9]/.test(p), message: 'Password must contain a number' },
];
function validatePassword(password: string) {
const errors = PASSWORD_RULES
.filter(rule => !rule.test(password))
.map(rule => rule.message);
return { valid: errors.length === 0, errors };
}
// Step 8: RED - Happy path test
it('should accept valid passwords', () => {
expect(validatePassword('ValidPass1')).toEqual({
valid: true,
errors: [],
});
});
// Already passes! ✅
# Run tests in watch mode
npm test -- --watch
# Run specific test file
npm test -- validatePassword
# Run with coverage
npm test -- --coverage
# Run and update snapshots
npm test -- --updateSnapshot
In these cases, write tests after, but still write them.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer