TDD Workflow Skill
The TDD Cycle
RED → GREEN → REFACTOR
↑__________________|1. RED: Write a Failing Test
- Write the smallest test that fails
- Test should fail for the right reason
- Ensure the test actually runs
2. GREEN: Make it Pass
- Write minimal code to pass the test
- Don't optimize yet
- It's okay if the code is ugly
3. REFACTOR: Clean Up
- Improve code structure
- Remove duplication
- Keep tests passing
TDD Best Practices
Test Naming Convention
should_[expected behavior]_when_[condition]Examples:
should_return_empty_array_when_no_itemsshould_throw_error_when_invalid_inputshould_calculate_total_when_items_present
Test Structure (AAA)
typescript
it('should calculate discount when coupon applied', () => {
// Arrange - Set up test data
const cart = new Cart();
cart.addItem({ price: 100 });
const coupon = new Coupon('10OFF', 10);
// Act - Execute the behavior
cart.applyCoupon(coupon);
// Assert - Verify the result
expect(cart.total).toBe(90);
});Test Isolation
- Each test should be independent
- No shared state between tests
- Use
beforeEachfor common setup - Clean up in
afterEach
TDD Workflow Example
Feature: Add item to cart
Step 1: RED
typescript
describe('Cart', () => {
it('should add item to cart', () => {
const cart = new Cart();
cart.addItem({ id: 1, name: 'Book', price: 29.99 });
expect(cart.items).toHaveLength(1);
});
});Run test → FAILS (Cart doesn't exist)
Step 2: GREEN
typescript
class Cart {
items = [];
addItem(item) {
this.items.push(item);
}
}Run test → PASSES
Step 3: REFACTOR
typescript
class Cart {
private _items: CartItem[] = [];
get items(): ReadonlyArray<CartItem> {
return this._items;
}
addItem(item: CartItem): void {
this._items.push(item);
}
}Run test → Still PASSES
Next iteration: Calculate total
Repeat the cycle for each new behavior.
When to Use TDD
Good for TDD
- Business logic
- Complex algorithms
- API endpoints
- State management
- Utility functions
Less Suitable
- UI layout (visual testing better)
- Database migrations
- External integrations (use integration tests)
- Exploratory/prototype code
Common TDD Mistakes
- Writing too much test - Start with smallest failing test
- Writing too much code - Only enough to pass
- Skipping refactor - Technical debt accumulates
- Testing implementation - Test behavior, not internals
- Ignoring failing tests - Fix or delete, never skip
Test Doubles
| Type | Purpose | Example |
|---|---|---|
| Stub | Return fixed data | jest.fn().mockReturnValue(42) |
| Mock | Verify interactions | expect(mock).toHaveBeenCalled() |
| Spy | Track calls | jest.spyOn(obj, 'method') |
| Fake | Simplified implementation | In-memory database |