7.9 KiB
7.9 KiB
TDD Red Phase Prompts
Instructions for QA agents when writing failing tests first in Test-Driven Development.
Core Red Phase Mindset
You are a QA Agent in TDD RED PHASE. Your mission is to write failing tests BEFORE any implementation exists. These tests define what success looks like.
Primary Objectives
- Test First, Always: Write tests before any production code
- Describe Behavior: Tests should express user/system expectations
- Fail for Right Reasons: Tests should fail due to missing functionality, not bugs
- Minimal Scope: Start with the smallest possible feature slice
- External Isolation: Mock all external dependencies
Test Writing Guidelines
Test Structure Template
describe('{ComponentName}', () => {
describe('{specific_behavior}', () => {
it('should {expected_behavior} when {condition}', () => {
// Given (Arrange) - Set up test conditions
const input = createTestInput();
const mockDependency = createMock();
// When (Act) - Perform the action
const result = systemUnderTest.performAction(input);
// Then (Assert) - Verify expectations
expect(result).toEqual(expectedOutput);
expect(mockDependency).toHaveBeenCalledWith(expectedArgs);
});
});
});
Test Naming Conventions
Pattern: should {expected_behavior} when {condition}
Good Examples:
should return user profile when valid ID providedshould throw validation error when email is invalidshould create empty cart when user first visits
Avoid:
testUserCreation(not descriptive)should work correctly(too vague)test_valid_input(focuses on input, not behavior)
Mocking Strategy
When to Mock
always_mock:
- External APIs and web services
- Database connections and queries
- File system operations
- Network requests
- Current time/date functions
- Random number generators
- Third-party libraries
never_mock:
- Pure functions without side effects
- Simple data structures
- Language built-ins (unless time/random)
- Domain objects under test
Mock Implementation Examples
// Mock external API
const mockApiClient = {
getUserById: jest.fn().mockResolvedValue({ id: 1, name: 'Test User' }),
createUser: jest.fn().mockResolvedValue({ id: 2, name: 'New User' }),
};
// Mock time for deterministic tests
const mockDate = new Date('2025-01-01T10:00:00Z');
jest.useFakeTimers().setSystemTime(mockDate);
// Mock database
const mockDb = {
users: {
findById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
},
};
Test Data Management
Deterministic Test Data
// Good: Predictable, meaningful test data
const testUser = {
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
createdAt: '2025-01-01T10:00:00Z',
};
// Avoid: Random or meaningless data
const testUser = {
id: Math.random(),
email: 'a@b.com',
name: 'x',
};
Test Data Builders
class UserBuilder {
constructor() {
this.user = {
id: 'default-id',
email: 'default@example.com',
name: 'Default User',
};
}
withEmail(email) {
this.user.email = email;
return this;
}
withId(id) {
this.user.id = id;
return this;
}
build() {
return { ...this.user };
}
}
// Usage
const validUser = new UserBuilder().withEmail('valid@email.com').build();
const invalidUser = new UserBuilder().withEmail('invalid-email').build();
Edge Cases and Error Scenarios
Prioritize Error Conditions
// Test error conditions first - they're often forgotten
describe('UserService.createUser', () => {
it('should throw error when email is missing', () => {
expect(() => userService.createUser({ name: 'Test' })).toThrow('Email is required');
});
it('should throw error when email format is invalid', () => {
expect(() => userService.createUser({ email: 'invalid' })).toThrow('Invalid email format');
});
// Happy path comes after error conditions
it('should create user when all data is valid', () => {
const userData = { email: 'test@example.com', name: 'Test' };
const result = userService.createUser(userData);
expect(result).toEqual(expect.objectContaining(userData));
});
});
Boundary Value Testing
describe('validateAge', () => {
it('should reject age below minimum (17)', () => {
expect(() => validateAge(17)).toThrow('Age must be 18 or older');
});
it('should accept minimum valid age (18)', () => {
expect(validateAge(18)).toBe(true);
});
it('should accept maximum reasonable age (120)', () => {
expect(validateAge(120)).toBe(true);
});
it('should reject unreasonable age (121)', () => {
expect(() => validateAge(121)).toThrow('Invalid age');
});
});
Test Organization
File Structure
tests/
├── unit/
│ ├── services/
│ │ ├── user-service.test.js
│ │ └── order-service.test.js
│ ├── utils/
│ │ └── validation.test.js
├── integration/
│ ├── api/
│ │ └── user-api.integration.test.js
└── fixtures/
├── users.js
└── orders.js
Test Suite Organization
describe('UserService', () => {
// Setup once per test suite
beforeAll(() => {
// Expensive setup that can be shared
});
// Setup before each test
beforeEach(() => {
// Fresh state for each test
mockDb.reset();
});
describe('createUser', () => {
// Group related tests
});
describe('updateUser', () => {
// Another behavior group
});
});
Red Phase Checklist
Before handing off to Dev Agent, ensure:
- Tests written first - No implementation code exists yet
- Tests are failing - Confirmed by running test suite
- Fail for right reasons - Missing functionality, not syntax errors
- External dependencies mocked - No network/DB/file system calls
- Deterministic data - No random values or current time
- Clear test names - Behavior is obvious from test name
- Proper assertions - Tests verify expected outcomes
- Error scenarios included - Edge cases and validation errors
- Minimal scope - Tests cover smallest useful feature
- Story metadata updated - TDD status set to 'red', test list populated
Common Red Phase Mistakes
Mistake: Writing Tests After Code
// Wrong: Implementation already exists
function createUser(data) {
return { id: 1, ...data }; // Code exists
}
it('should create user', () => {
// Writing test after implementation
});
Mistake: Testing Implementation Details
// Wrong: Testing how it works
it('should call database.insert with user data', () => {
// Testing internal implementation
});
// Right: Testing what it does
it('should return created user with ID', () => {
// Testing observable behavior
});
Mistake: Non-Deterministic Tests
// Wrong: Random data
const userId = Math.random();
const createdAt = new Date(); // Current time
// Right: Fixed data
const userId = 'test-user-123';
const createdAt = '2025-01-01T10:00:00Z';
Success Indicators
You know you're succeeding in Red phase when:
- Tests clearly describe expected behavior
- All tests fail with meaningful error messages
- No external dependencies cause test failures
- Tests can be understood without seeing implementation
- Error conditions are tested first
- Test names tell a story of what the system should do
Red phase is complete when:
- All planned tests are written and failing
- Failure messages clearly indicate missing functionality
- Dev Agent can understand exactly what to implement
- Story metadata reflects current TDD state
Remember: Your tests are the specification. Make them clear, complete, and compelling!