Building Your Learning Module...
Getting things ready for you!
Find videos you like?
Save to resource drawer for future reference!
TDD is a development approach where you write tests first, before writing the actual code. The test initially fails, then you write just enough code to make it pass, and finally refactor for quality.
Write a failing test first
Make the test pass
Improve the code
// STEP 1: 🔴 RED - Write failing test first
test('reverses a string', () => {
expect(reverse('hello')).toBe('olleh');
});
// ❌ Test fails: reverse is not defined
// STEP 2: 🟢 GREEN - Write minimal code to pass
function reverse(str) {
return str.split('').reverse().join('');
}
// ✅ Test passes!
// STEP 3: 🔵 REFACTOR - Improve (optional in this case)
// Code is already clean, move to next test
// NEXT TEST: 🔴 RED - Add edge case
test('handles empty string', () => {
expect(reverse('')).toBe('');
});
// ✅ Already passes with current implementation!
// NEXT TEST: 🔴 RED - Another edge case
test('handles single character', () => {
expect(reverse('a')).toBe('a');
});
// ✅ Passes!
// TDD complete - all tests pass, feature is done// Test 1: 🔴 RED
test('adds two numbers', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});
// ❌ Fails: Calculator doesn't exist
// 🟢 GREEN: Minimal implementation
class Calculator {
add(a, b) {
return a + b;
}
}
// ✅ Passes!
// Test 2: 🔴 RED
test('subtracts two numbers', () => {
const calc = new Calculator();
expect(calc.subtract(5, 3)).toBe(2);
});
// ❌ Fails: subtract doesn't exist
// 🟢 GREEN: Add subtract method
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
}
// ✅ Passes!
// Test 3: 🔴 RED - Chain operations
test('chains operations', () => {
const calc = new Calculator();
const result = calc.add(10, 5).subtract(3).getResult();
expect(result).toBe(12);
});
// ❌ Fails: Methods don't return this
// 🟢 GREEN: Enable chaining
class Calculator {
constructor() {
this.result = 0;
}
add(a, b = null) {
if (b === null) {
this.result += a;
} else {
this.result = a + b;
}
return this;
}
subtract(a, b = null) {
if (b === null) {
this.result -= a;
} else {
this.result = a - b;
}
return this;
}
getResult() {
return this.result;
}
}
// ✅ All tests pass!
// 🔵 REFACTOR: Clean up implementation (if needed)// Feature: Get unique values from array
// Test 1: 🔴 RED - Basic functionality
test('returns unique values', () => {
expect(unique([1, 2, 2, 3, 3, 3])).toEqual([1, 2, 3]);
});
// ❌ Fails: unique doesn't exist
// 🟢 GREEN: Minimal implementation
function unique(arr) {
return [...new Set(arr)];
}
// ✅ Passes!
// Test 2: 🔴 RED - Empty array
test('handles empty array', () => {
expect(unique([])).toEqual([]);
});
// ✅ Already passes!
// Test 3: 🔴 RED - Non-primitives
test('handles objects', () => {
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const result = unique([obj1, obj2, obj1]);
expect(result).toEqual([obj1, obj2]);
});
// ✅ Passes! Set handles object references
// Test 4: 🔴 RED - Custom comparator
test('uses custom comparator', () => {
const arr = [{ id: 1 }, { id: 2 }, { id: 1 }];
const result = unique(arr, (a, b) => a.id === b.id);
expect(result).toEqual([{ id: 1 }, { id: 2 }]);
});
// ❌ Fails: No comparator support
// 🟢 GREEN: Add comparator
function unique(arr, compareFn) {
if (!compareFn) {
return [...new Set(arr)];
}
return arr.reduce((acc, item) => {
const exists = acc.some(x => compareFn(x, item));
return exists ? acc : [...acc, item];
}, []);
}
// ✅ All tests pass!
// 🔵 REFACTOR: Already clean!Begin with the simplest test case. Don't try to handle all edge cases at once.
Write one failing test, make it pass, then move to the next. Don't write multiple failing tests.
Take tiny steps. Each cycle should be quick (1-5 minutes). Don't write too much code at once.
Tests give you safety to refactor. If tests pass, your refactoring didn't break anything.
Focus on what the code should do, not how it does it. This makes tests more resilient to refactoring.
// Feature: Email validator
// Test 1: 🔴 RED - Valid email
test('validates correct email', () => {
expect(isValidEmail('test@example.com')).toBe(true);
});
// ❌ Fails
// 🟢 GREEN
function isValidEmail(email) {
return email.includes('@');
}
// ✅ Passes (but too simple)
// Test 2: 🔴 RED - Must have domain
test('requires domain', () => {
expect(isValidEmail('test@')).toBe(false);
});
// ❌ Fails
// 🟢 GREEN
function isValidEmail(email) {
return email.includes('@') && email.split('@')[1].length > 0;
}
// ✅ Passes
// Test 3: 🔴 RED - Must have username
test('requires username', () => {
expect(isValidEmail('@example.com')).toBe(false);
});
// ❌ Fails
// 🟢 GREEN
function isValidEmail(email) {
const parts = email.split('@');
return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
}
// ✅ Passes
// Test 4: 🔴 RED - Domain must have dot
test('domain must have dot', () => {
expect(isValidEmail('test@example')).toBe(false);
expect(isValidEmail('test@example.com')).toBe(true);
});
// ❌ Fails
// 🟢 GREEN
function isValidEmail(email) {
const parts = email.split('@');
if (parts.length !== 2) return false;
const [username, domain] = parts;
return username.length > 0 && domain.includes('.');
}
// ✅ All tests pass!
// 🔵 REFACTOR: Use regex for better validation
function isValidEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
// ✅ All tests still pass, cleaner implementation!Test what you want to build
It should fail initially
Write minimal code
Just make the test pass
Clean up implementation
Tests give you confidence
One feature at a time
Small, incremental steps