Master Test Driven Development
All-in-one skill covering implementing, feature, bugfix, before. Includes structured workflows, validation checks, and reusable patterns for development.
Master Test-Driven Development
An advanced TDD skill covering testing philosophy, test doubles, property-based testing, mutation testing, and strategies for applying TDD to complex systems including databases, APIs, and distributed architectures.
When to Use This Skill
Choose this skill when:
- Practicing advanced TDD techniques beyond basic Red-Green-Refactor
- Implementing test doubles (mocks, stubs, spies, fakes) correctly
- Applying property-based testing for algorithmic code
- Using mutation testing to verify test suite quality
- Designing testable architectures from the ground up
Consider alternatives when:
- Learning basic TDD → use a TDD workflow skill
- Fixing existing failing tests → use a test-fixing skill
- Setting up a test framework → use a testing patterns skill
- Running tests in CI/CD → use a DevOps skill
Quick Start
// Advanced TDD: Property-Based Testing import { fc } from '@fast-check/vitest'; import { describe, it, expect } from 'vitest'; describe('sort function', () => { // Traditional example-based test it('sorts numbers ascending', () => { expect(sort([3, 1, 2])).toEqual([1, 2, 3]); }); // Property-based: output is always sorted it.prop([fc.array(fc.integer())])('output is always sorted', (arr) => { const sorted = sort(arr); for (let i = 1; i < sorted.length; i++) { expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i - 1]); } }); // Property: same elements, same length it.prop([fc.array(fc.integer())])('preserves all elements', (arr) => { const sorted = sort(arr); expect(sorted.length).toBe(arr.length); expect(sorted.sort()).toEqual([...arr].sort()); }); });
Core Concepts
Test Double Types
| Double | Purpose | Behavior | Verification |
|---|---|---|---|
| Stub | Return canned responses | Predefined return values | No verification |
| Mock | Verify interactions | Predefined + expectations | Verify calls made |
| Spy | Record real calls | Wraps real implementation | Inspect call log |
| Fake | Working simplified version | In-memory DB, fake server | Via behavior |
| Dummy | Fill parameter lists | No behavior | Not used |
Test Double Implementation
// Fake: Working in-memory implementation class FakeUserRepository implements UserRepository { private users = new Map<string, User>(); async findById(id: string): Promise<User | null> { return this.users.get(id) || null; } async save(user: User): Promise<void> { this.users.set(user.id, { ...user }); } async findByEmail(email: string): Promise<User | null> { return [...this.users.values()].find(u => u.email === email) || null; } } // Using the fake in tests (no mocking library needed) describe('UserService', () => { it('creates a user with hashed password', async () => { const repo = new FakeUserRepository(); const service = new UserService(repo); await service.createUser('[email protected]', 'password123'); const user = await repo.findByEmail('[email protected]'); expect(user).not.toBeNull(); expect(user!.password).not.toBe('password123'); // hashed }); });
Mutation Testing
# Run mutation testing with Stryker (JavaScript/TypeScript) npx stryker run # What it does: # 1. Makes small changes (mutations) to your source code # 2. Runs your tests against each mutation # 3. Reports which mutations survived (tests didn't catch them) # Example mutations: # - Replace > with >= (boundary mutation) # - Replace true with false (boolean mutation) # - Remove function call (statement deletion) # - Replace + with - (arithmetic mutation) # A survived mutation means your tests don't verify that behavior
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
testDoubleLibrary | string | 'vitest' | Mocking: vitest built-in, sinon, or testdouble |
propertyTestRuns | number | 100 | Number of property-based test iterations |
mutationThreshold | number | 80 | Minimum mutation score percentage |
coverageThreshold | number | 90 | Minimum line coverage |
testIsolation | string | 'strict' | Isolation: strict (reset all) or relaxed |
snapshotStrategy | string | 'avoid' | Snapshot testing: avoid, selective, or liberal |
Best Practices
-
Prefer fakes over mocks for collaborator testing — Fakes (in-memory implementations of interfaces) test real behavior without external dependencies. Mocks verify interaction patterns, which couples tests to implementation and breaks on refactoring.
-
Use property-based testing for algorithms and data transformations — Example-based tests check specific cases; property-based tests verify invariants across thousands of random inputs. Properties like "sort preserves length" and "encode then decode equals original" catch edge cases you'd never think to write.
-
Run mutation testing to validate your test suite — 100% code coverage can still miss bugs. Mutation testing modifies your code and checks if tests detect the changes. A high mutation score (>80%) means your assertions actually verify correctness.
-
Test behavior through public interfaces, not internal methods — If a refactoring changes internal structure but preserves behavior, tests should still pass. Testing private methods creates brittle tests that break on every structural change.
-
Use test data builders for complex object construction — Instead of constructing test objects with dozens of fields, create builder functions with sensible defaults. Override only the fields relevant to each test case.
Common Issues
Mock setup is longer than the test itself — Over-mocking indicates tight coupling in production code. Refactor to depend on interfaces with simple contracts. If a function needs 5 mocks, it has too many dependencies.
Property-based tests find bugs but are hard to reproduce — Most property testing libraries print the seed and shrunk example on failure. Save these and add them as explicit regression tests. This gives you the best of both worlds: discovery and reproducibility.
Mutation score is low despite high coverage — Coverage measures which lines execute, not what's verified. Add assertions for return values, state changes, and error conditions. Every conditional branch should have a test that fails if the condition is removed.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.