TDD Guide
Red-Green-Refactor Cycle
// STEP 1 (RED): Write a failing test
// test for a feature that doesn't exist yet
test('FizzBuzz returns "Fizz" for multiples of 3', () => {
expect(fizzBuzz(3)).toBe('Fizz'); // ❌ fizzBuzz is not defined
expect(fizzBuzz(6)).toBe('Fizz');
expect(fizzBuzz(9)).toBe('Fizz');
});
// STEP 2 (GREEN): Write the minimum code to make it pass
function fizzBuzz(n) {
if (n % 3 === 0) return 'Fizz'; // ✅ only what's needed
return String(n);
}
// STEP 3 (REFACTOR): Improve the code without breaking tests
// Now add more tests, then more code:
test('FizzBuzz returns "Buzz" for multiples of 5', () => {
expect(fizzBuzz(5)).toBe('Buzz');
expect(fizzBuzz(10)).toBe('Buzz');
});
test('FizzBuzz returns "FizzBuzz" for multiples of 15', () => {
expect(fizzBuzz(15)).toBe('FizzBuzz');
});
// Final implementation:
function fizzBuzz(n) {
if (n % 15 === 0) return 'FizzBuzz';
if (n % 3 === 0) return 'Fizz';
if (n % 5 === 0) return 'Buzz';
return String(n);
}
TDD Approaches
| Approach | Direction | Start With | Good For |
|---|---|---|---|
| Inside-Out (Detroit/Chicago) | Units first, integrate later | Domain objects, pure logic | Well-understood domain, algorithms |
| Outside-In (London/Mockist) | Acceptance test first, mock collaborators | High-level acceptance test | API-driven design, unknown internals |
| BDD (Behavior-Driven) | User scenarios first | Gherkin feature files | Cross-team communication, acceptance criteria |
BDD with Gherkin
# Feature file (Gherkin syntax)
Feature: User login
As a registered user
I want to log in to my account
So that I can access my dashboard
Scenario: Successful login
Given I am on the login page
When I enter email "user@example.com" and password "correct123"
And I click the "Log In" button
Then I should be redirected to the dashboard
And I should see "Welcome back, User"
Scenario: Failed login with wrong password
Given I am on the login page
When I enter email "user@example.com" and password "wrong"
And I click the "Log In" button
Then I should see "Invalid email or password"
And I should remain on the login page
# Step definition (JavaScript — Cucumber.js)
Given('I am on the login page', async () => {
await page.goto('/login');
});
When('I enter email {string} and password {string}', async (email, password) => {
await page.fill('#email', email);
await page.fill('#password', password);
});