Agent skill
tdd-full-coverage
Use when implementing features or fixes - test-driven development with RED-GREEN-REFACTOR cycle and full code coverage requirement
Install this agent skill to your Project
npx add-skill https://github.com/troykelly/codex-skills/tree/main/skills/tdd-full-coverage
SKILL.md
TDD Full Coverage
Overview
Test-Driven Development with full code coverage.
Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.
Announce at start: "I'm using TDD to implement this feature."
The Iron Law
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Wrote code before a test? Delete it. Start over.
Red-Green-Refactor Cycle
┌─────────────────────────────────────────────┐
│ │
▼ │
┌───────┐ ┌───────┐ ┌──────────┐ │
│ RED │────►│ GREEN │────►│ REFACTOR │─────────┘
└───────┘ └───────┘ └──────────┘
Write Write Clean
failing minimal up code
test code (stay green)
RED: Write Failing Test
Write ONE test for ONE behavior.
// Test one specific thing
test('rejects empty email', async () => {
const result = await validateEmail('');
expect(result.valid).toBe(false);
expect(result.error).toBe('Email is required');
});
Verify RED: Watch It Fail
MANDATORY. Never skip.
pnpm test --grep "rejects empty email"
Confirm:
- Test FAILS (not errors)
- Fails for EXPECTED reason (feature missing, not typo)
- Error message is what you expect
If test passes → You're testing existing behavior. Fix the test.
GREEN: Minimal Code
Write the SIMPLEST code to pass the test.
function validateEmail(email: string): ValidationResult {
if (!email) {
return { valid: false, error: 'Email is required' };
}
return { valid: true };
}
Don't add:
- Error handling for cases you haven't tested
- Configuration options you don't need yet
- Optimizations
Verify GREEN: Watch It Pass
MANDATORY.
pnpm test --grep "rejects empty email"
Confirm:
- Test PASSES
- All other tests still pass
- No errors or warnings
REFACTOR: Clean Up
After green, improve code quality:
- Remove duplication
- Improve names
- Extract helpers
Keep tests green during refactoring.
Repeat
Write next failing test for next behavior.
Coverage Requirements
Target: 100% for New Code
# Check coverage
pnpm test --coverage
# Verify new code is covered
# Lines: 100%
# Branches: 100%
# Functions: 100%
# Statements: 100%
What 100% Means
| Covered | Not Covered (Fix It) |
|---|---|
| All branches tested | Some if/else paths missed |
| All functions called | Unused functions |
| All error handlers triggered | Error paths untested |
| All edge cases verified | Only happy path |
Acceptable Exceptions
These MAY have lower coverage (discuss with team):
- Configuration files
- Type definitions only
- Auto-generated code
- Third-party integration code (mock at boundary)
Document exceptions in coverage config:
// jest.config.js
module.exports = {
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
coveragePathIgnorePatterns: [
'/node_modules/',
'/generated/',
'config.ts',
],
};
Integration Testing Against Local Services
Core principle: Unit tests with mocks are necessary but not sufficient. You MUST ALSO test against real services.
The Two-Layer Testing Requirement
| Layer | Purpose | Uses Mocks? | Uses Real Services? |
|---|---|---|---|
| Unit Tests (TDD) | Verify logic, enable RED-GREEN-REFACTOR | YES | No |
| Integration Tests | Verify real service behavior | No | YES |
Both layers are REQUIRED. Unit tests alone miss real-world failures. Integration tests alone are too slow for TDD.
The Problem We're Solving
We've experienced 80% failure rates with ORM migrations because:
- Unit tests with mocks pass
- Real database rejects the migration
- CI discovers the bug instead of local testing
Mocks don't catch: Schema mismatches, constraint violations, migration failures, connection issues, transaction behavior.
When Integration Tests Are Required
| Code Change | Unit Tests (with mocks) | Integration Tests (with real services) |
|---|---|---|
| Database model/migration | ✅ Required | ✅ Also required |
| Repository/ORM layer | ✅ Required | ✅ Also required |
| Cache operations | ✅ Required | ✅ Also required |
| Pub/sub messages | ✅ Required | ✅ Also required |
| Queue workers | ✅ Required | ✅ Also required |
Local Service Testing Protocol
After completing TDD cycle (unit tests with mocks):
- Ensure services are running (
docker-compose up -d) - Run integration tests against real services
- Verify migrations apply (
pnpm migrate) - Verify in local environment before pushing
Example: Database Testing
// LAYER 1: Unit tests with mocks (TDD cycle)
describe('UserRepository (unit)', () => {
const mockDb = { query: jest.fn() };
it('calls correct SQL for findById', async () => {
mockDb.query.mockResolvedValue([{ id: 1, email: 'test@example.com' }]);
const user = await userRepo.findById(1);
expect(mockDb.query).toHaveBeenCalledWith('SELECT * FROM users WHERE id = $1', [1]);
});
});
// LAYER 2: Integration tests with real postgres (ALSO required)
describe('UserRepository (integration)', () => {
beforeAll(async () => {
await db.migrate.latest();
});
it('actually persists and retrieves users', async () => {
await userRepo.create({ email: 'test@example.com' });
const user = await userRepo.findByEmail('test@example.com');
expect(user).toBeDefined();
expect(user.email).toBe('test@example.com');
});
it('enforces unique email constraint', async () => {
await userRepo.create({ email: 'unique@example.com' });
// Real postgres will throw - mocks won't catch this
await expect(
userRepo.create({ email: 'unique@example.com' })
).rejects.toThrow(/unique constraint/);
});
});
Skill: local-service-testing
Test Quality
Good Tests
// GOOD: Clear name, tests one thing
test('calculates tax for positive amount', () => {
const result = calculateTax(100, 0.08);
expect(result).toBe(8);
});
test('returns zero tax for zero amount', () => {
const result = calculateTax(0, 0.08);
expect(result).toBe(0);
});
test('throws for negative amount', () => {
expect(() => calculateTax(-100, 0.08)).toThrow('Amount must be positive');
});
Bad Tests
// BAD: Tests multiple things
test('calculateTax works', () => {
expect(calculateTax(100, 0.08)).toBe(8);
expect(calculateTax(0, 0.08)).toBe(0);
expect(() => calculateTax(-100, 0.08)).toThrow();
});
// BAD: Tests mock, not real code
test('calls the tax service', () => {
const mockTaxService = jest.fn().mockReturnValue(8);
const result = calculateTax(100, 0.08);
expect(mockTaxService).toHaveBeenCalled(); // Testing mock, not behavior
});
Testing Patterns
Arrange-Act-Assert
test('description', () => {
// Arrange - set up test data
const user = createTestUser({ email: 'test@example.com' });
const input = { userId: user.id, action: 'update' };
// Act - perform the action
const result = processAction(input);
// Assert - verify the outcome
expect(result.success).toBe(true);
expect(result.timestamp).toBeDefined();
});
Testing Errors
test('throws for invalid input', () => {
expect(() => validateInput(null)).toThrow(ValidationError);
expect(() => validateInput(null)).toThrow('Input is required');
});
test('async throws for invalid input', async () => {
await expect(asyncValidate(null)).rejects.toThrow(ValidationError);
});
Testing Side Effects
test('logs error on failure', async () => {
const logSpy = jest.spyOn(logger, 'error');
await processWithFailure();
expect(logSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to process')
);
});
Mocking Guidelines
When to Mock
| Mock | Don't Mock |
|---|---|
| External APIs | Your own code |
| Database (integration) | Simple functions |
| File system | Pure logic |
| Time/dates | Deterministic code |
| Network requests | Internal modules |
Mock at Boundaries
// GOOD: Mock the external boundary
const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ data: 'test' }))
);
// BAD: Mock internal implementation
const internalMock = jest.spyOn(utils, 'internalHelper');
Debugging Test Failures
| Problem | Solution |
|---|---|
| Test passes when should fail | Check assertion (expect syntax) |
| Test fails unexpectedly | Check test isolation (cleanup) |
| Flaky tests | Remove timing dependencies |
| Hard to test | Improve code design |
Checklist
Before completing a feature:
- Every function has at least one test
- Watched each test fail before implementing
- Each failure was for expected reason
- Wrote minimal code to pass
- All tests pass
- Coverage is 100% for new code
- No skipped tests
- Tests are isolated (no order dependency)
- Error cases are tested
- Integration tests ran against local services (not mocks)
- All service-dependent code verified locally
Integration
This skill is called by:
issue-driven-development- Step 7, 8, 11
This skill uses:
strict-typing- Tests should be typedinline-documentation- Document test utilities
This skill ensures:
- Verified behavior
- Regression prevention
- Refactoring safety
- Documentation through tests
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
hook-development
Use when the user wants to create Codex workflow hooks (pre/post run gates, tool-use validators, stop checks) or needs guidance on hook scripts and hooks.json configuration.
sentry-setup-ai-monitoring
Setup Sentry AI Agent Monitoring in any project. Use this when asked to add AI monitoring, track LLM calls, monitor AI agents, or instrument OpenAI/Anthropic/Vercel AI/LangChain/Google GenAI. Automatically detects installed AI SDKs and configures the appropriate Sentry integration.
agent-development
Use when the user wants to design Codex agent equivalents (specialized workers/profiles/prompt files), define triggering conditions, or build reusable agent prompts and validation tools.
skill-development
Use when the user wants to create or refine Codex skills, improve skill descriptions, organize skill resources, or follow Codex skill best practices.
sentry-setup-logging
Setup Sentry Logging in any project. Use this when asked to add Sentry logs, enable structured logging, setup console log capture, or integrate logging with Sentry. Supports JavaScript, TypeScript, Python, Ruby, React, Next.js, and other frameworks.
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Didn't find tool you were looking for?