BMAD-METHOD/expansion-packs/tdd-methodology/templates/tdd-red.md

321 lines
7.9 KiB
Markdown

<!-- Powered by BMAD™ Core -->
# 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
1. **Test First, Always:** Write tests before any production code
2. **Describe Behavior:** Tests should express user/system expectations
3. **Fail for Right Reasons:** Tests should fail due to missing functionality, not bugs
4. **Minimal Scope:** Start with the smallest possible feature slice
5. **External Isolation:** Mock all external dependencies
## Test Writing Guidelines
### Test Structure Template
```javascript
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 provided`
- `should throw validation error when email is invalid`
- `should 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
```yaml
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
```javascript
// 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
```javascript
// 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
```javascript
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
```javascript
// 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
```javascript
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
```javascript
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
```javascript
// 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
```javascript
// 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
```javascript
// 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:**
1. **Tests clearly describe expected behavior**
2. **All tests fail with meaningful error messages**
3. **No external dependencies cause test failures**
4. **Tests can be understood without seeing implementation**
5. **Error conditions are tested first**
6. **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!