Mutation Testing
Set up advanced mutation testing
You’re building a financial calculation engine for a investment platform. Requirements include compound interest calculations, portfolio rebalancing algorithms, tax optimization strategies, and risk assessment. The calculations must be 100% accurate, handle edge cases, and be thoroughly documented. Your team requires TDD approach with >95% test coverage.
By completing this lesson, you’ll master:
Build a calculation engine using pure TDD:
Start with requirements analysis:
"I need to build a financial calculation engine with these features:1. Compound interest calculator2. Portfolio rebalancing3. Tax loss harvesting4. Risk assessment (Sharpe ratio, etc.)
Help me create a comprehensive test plan following TDD principles.What test categories and scenarios should I consider?"
"Design the test file structure for:- Unit tests for each calculation- Integration tests for workflows- Property-based tests for mathematical properties- Performance tests for large portfolios- Snapshot tests for reportsCreate the directory structure and test templates"
Switch to Agent mode:
"Create test utilities and helpers:- Test data factories for portfolios- Assertion helpers for financial values- Mock data generators- Custom matchers for precision- Test fixtures for common scenarios"
Example test utilities:
export const expectFinanciallyEqual = ( actual: number, expected: number, precision: number = 2) => { const multiplier = Math.pow(10, precision); expect(Math.round(actual * multiplier)).toBe( Math.round(expected * multiplier) );};
export const createPortfolio = (overrides?: Partial<Portfolio>): Portfolio => ({ id: faker.string.uuid(), name: faker.company.name() + ' Portfolio', holdings: [ { symbol: 'AAPL', shares: faker.number.int({ min: 10, max: 1000 }), costBasis: faker.number.float({ min: 100, max: 200 }), }, { symbol: 'GOOGL', shares: faker.number.int({ min: 5, max: 500 }), costBasis: faker.number.float({ min: 1000, max: 3000 }), }, ], cash: faker.number.float({ min: 1000, max: 100000 }), ...overrides,});
export const createMarketData = (): MarketData => ({ AAPL: { price: 150.00, change: 0.02 }, GOOGL: { price: 2800.00, change: -0.01 }, // ... more symbols});
"Write comprehensive tests for compound interest calculation:- Basic calculation with annual compounding- Different compounding frequencies (monthly, daily, continuous)- Edge cases (negative rates, zero principal, extreme values)- Precision and rounding behavior- Invalid input handlingInclude the mathematical formula in comments"
Example test suite:
describe('CompoundInterestCalculator', () => { describe('calculate()', () => { // Formula: A = P(1 + r/n)^(nt) // A = final amount, P = principal, r = rate, n = compounds per year, t = time
it('should calculate simple annual compound interest', () => { const result = calculateCompoundInterest({ principal: 1000, rate: 0.05, time: 10, compoundingFrequency: 1, });
// 1000 * (1 + 0.05/1)^(1*10) = 1628.89 expectFinanciallyEqual(result.finalAmount, 1628.89); expectFinanciallyEqual(result.totalInterest, 628.89); });
it('should handle monthly compounding', () => { const result = calculateCompoundInterest({ principal: 1000, rate: 0.05, time: 10, compoundingFrequency: 12, });
// 1000 * (1 + 0.05/12)^(12*10) = 1647.01 expectFinanciallyEqual(result.finalAmount, 1647.01); });
it('should handle continuous compounding', () => { const result = calculateCompoundInterest({ principal: 1000, rate: 0.05, time: 10, compoundingFrequency: 'continuous', });
// P * e^(rt) = 1000 * e^(0.05*10) = 1648.72 expectFinanciallyEqual(result.finalAmount, 1648.72); });
describe('edge cases', () => { it('should handle zero principal', () => { const result = calculateCompoundInterest({ principal: 0, rate: 0.05, time: 10, }); expect(result.finalAmount).toBe(0); });
it('should handle negative interest rates', () => { const result = calculateCompoundInterest({ principal: 1000, rate: -0.02, time: 5, }); expectFinanciallyEqual(result.finalAmount, 904.84); });
it('should handle very large numbers without overflow', () => { const result = calculateCompoundInterest({ principal: 1e10, rate: 0.15, time: 50, }); expect(result.finalAmount).toBeGreaterThan(0); expect(result.finalAmount).toBeLessThan(Infinity); }); });
describe('validation', () => { it('should throw for invalid principal', () => { expect(() => calculateCompoundInterest({ principal: NaN, rate: 0.05, time: 10, }) ).toThrow('Principal must be a valid number'); });
it('should throw for invalid time period', () => { expect(() => calculateCompoundInterest({ principal: 1000, rate: 0.05, time: -5, }) ).toThrow('Time period must be positive'); }); }); });});
@test-utils/financial.ts"Create tests for portfolio rebalancing algorithm:- Target allocation rebalancing- Threshold-based rebalancing- Tax-aware rebalancing- Transaction cost minimization- Edge cases (illiquid assets, constraints)Test both the algorithm and its optimizations"
"Write tests for risk metrics:- Sharpe ratio calculation- Standard deviation of returns- Maximum drawdown- Value at Risk (VaR)- Beta calculationInclude tests for different time periods and data quality"
"Create property-based tests for financial calculations:- Mathematical properties (associativity, distributivity)- Invariants (portfolio value = sum of holdings + cash)- Bounds (returns between -100% and +∞)- Monotonicity (more time = more compound interest)Use fast-check library"
Example property tests:
import fc from 'fast-check';
describe('Financial Calculations Properties', () => { describe('Compound Interest Properties', () => { it('should always increase with positive rate and time', () => { fc.assert( fc.property( fc.float({ min: 100, max: 1000000 }), // principal fc.float({ min: 0.001, max: 0.20 }), // rate fc.integer({ min: 1, max: 50 }), // years (principal, rate, time) => { const result = calculateCompoundInterest({ principal, rate, time, });
expect(result.finalAmount).toBeGreaterThan(principal); expect(result.totalInterest).toBeGreaterThan(0); } ) ); });
it('should be monotonic with respect to time', () => { fc.assert( fc.property( fc.float({ min: 100, max: 1000000 }), fc.float({ min: 0.001, max: 0.20 }), fc.integer({ min: 1, max: 30 }), fc.integer({ min: 1, max: 20 }), (principal, rate, time1, extraTime) => { const time2 = time1 + extraTime;
const result1 = calculateCompoundInterest({ principal, rate, time: time1 }); const result2 = calculateCompoundInterest({ principal, rate, time: time2 });
expect(result2.finalAmount).toBeGreaterThan(result1.finalAmount); } ) ); }); });
describe('Portfolio Properties', () => { it('should maintain total value through rebalancing', () => { fc.assert( fc.property( fc.array( fc.record({ symbol: fc.string({ minLength: 1, maxLength: 5 }), shares: fc.integer({ min: 1, max: 1000 }), price: fc.float({ min: 1, max: 5000 }), }), { minLength: 2, maxLength: 10 } ), fc.float({ min: 0, max: 100000 }), (holdings, cash) => { const portfolio = { holdings, cash }; const totalBefore = calculatePortfolioValue(portfolio);
const rebalanced = rebalancePortfolio(portfolio, { STOCKS: 0.6, BONDS: 0.3, CASH: 0.1, });
const totalAfter = calculatePortfolioValue(rebalanced);
// Account for transaction costs expect(totalAfter).toBeGreaterThanOrEqual(totalBefore * 0.99); expect(totalAfter).toBeLessThanOrEqual(totalBefore); } ) ); }); });});
"Generate tests for system invariants:- Conservation of value in transactions- No negative holdings after rebalancing- Risk metrics within valid ranges- Tax calculations never negativeCreate property tests for each invariant"
"Create fuzz tests for:- Input validation boundaries- Calculation precision limits- Concurrent operations- State consistencyGenerate random but valid test data"
{ "mcpServers": { "playwright": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-puppeteer"] } }}
"Using Playwright MCP, test the investment dashboard:1. Navigate to localhost:30002. Log in with test credentials3. Verify portfolio value displays correctly4. Click 'Rebalance' button5. Take screenshot of confirmation modal6. Verify success message appears"
// MCP handles browser automation"Using Playwright MCP:- Fill the compound interest form with principal=10000, rate=5%, time=10- Click Calculate button- Verify result shows $16,288.95- Take screenshot for visual regression test"
"Using Playwright MCP:- Navigate to component library page- Screenshot each calculator component- Compare with baseline images- Report any visual differences"
"Test responsive design with Playwright MCP:- Set viewport to mobile (375x667)- Screenshot dashboard- Set viewport to tablet (768x1024)- Screenshot dashboard- Set viewport to desktop (1920x1080)- Screenshot dashboard- Verify all layouts render correctly"
"Using Playwright MCP, test in different browsers:- Run test suite in Chromium- Run test suite in Firefox- Run test suite in WebKit- Compare results and timing"
"Using Playwright MCP, run accessibility audit:- Navigate to each page- Run axe accessibility scan- Check color contrast ratios- Verify keyboard navigation- Test screen reader announcements- Generate accessibility report"
"Using Playwright MCP, measure performance:- Navigate to dashboard with performance tracing- Measure time to interactive (TTI)- Check largest contentful paint (LCP)- Monitor cumulative layout shift (CLS)- Generate performance report with metrics"
// Real user monitoring simulation"Simulate user journey with Playwright MCP:1. Land on homepage (measure load time)2. Navigate to calculator (measure transition)3. Perform 10 calculations (measure responsiveness)4. Generate report (measure rendering time)Record all performance metrics"
Parallel Testing:
"Using Playwright MCP, run tests in parallel:- Test 1: Portfolio rebalancing flow- Test 2: Tax calculation workflow- Test 3: Report generation- Test 4: User settings updateRun all simultaneously and report results"
API Mocking:
"Using Playwright MCP with network interception:- Mock market data API responses- Test error scenarios (500, timeout)- Verify error handling UI- Test loading states- Validate retry mechanisms"
State Management Testing:
"Test complex state with Playwright MCP:- Open app in two browser tabs- Update portfolio in tab 1- Verify tab 2 reflects changes- Test optimistic updates- Verify conflict resolution"
"Create integration tests for complete workflows:- New investor onboarding with initial portfolio- Monthly rebalancing with market updates- Tax loss harvesting at year end- Performance reporting generationTest the full flow with realistic data"
Example integration test:
describe('Investor Workflow Integration', () => { let investor: Investor; let marketData: MarketDataService;
beforeEach(() => { investor = createInvestor({ initialDeposit: 100000, riskTolerance: 'moderate', taxBracket: 0.32, }); marketData = new MockMarketDataService(); });
it('should handle complete monthly rebalancing workflow', async () => { // Initial portfolio creation const portfolio = await createPortfolio(investor, { strategy: 'aggressive-growth', constraints: { minCash: 5000, maxSinglePosition: 0.25, }, });
expect(portfolio.holdings).toHaveLength(15); expectPortfolioValid(portfolio);
// Simulate month of market movements await marketData.simulateMonth();
// Check for rebalancing need const analysis = analyzePortfolio(portfolio, marketData); expect(analysis.rebalancingNeeded).toBe(true); expect(analysis.drift).toBeGreaterThan(0.05);
// Execute rebalancing const trades = await rebalancePortfolio(portfolio, { marketData, taxAware: true, minTradeSize: 100, });
expect(trades).toHaveLength(8); expect(trades.every(t => t.taxImpact !== undefined)).toBe(true);
// Apply trades and verify const newPortfolio = applyTrades(portfolio, trades); expectPortfolioValid(newPortfolio); expect(calculateDrift(newPortfolio)).toBeLessThan(0.01); });});
"Create performance benchmarks:- Calculate 10,000 portfolios in under 1 second- Rebalance 1,000 asset portfolio in under 100ms- Generate tax report for 1 year in under 500ms- Risk calculations on 5 years of data in under 50msInclude memory usage tests"
"Design load tests for:- Concurrent portfolio calculations- Market data updates during rebalancing- Multiple tax optimization requests- Report generation under loadEnsure thread safety and accuracy"
"Create comprehensive test documentation:- Test coverage report with explanations- Edge case catalog with examples- Performance benchmark baselines- Test data generation guide- Troubleshooting guide for failures"
"Build visual test reporting:- Coverage heat maps- Performance trend graphs- Flaky test detection- Test execution timeline- Failure pattern analysis"
"Create advanced test helpers:- Snapshot testing for calculations- Golden file testing for reports- Mutation testing setup- Contract testing for APIs- Regression test automation"
Use AI and MCP to find edge cases:
// Method 1: Using Playwright MCP for UI testing"Using Playwright MCP, explore the calculator UI and find:- Input validation edge cases- UI states not covered by unit tests- Accessibility issues- Performance bottlenecks in rendering"
// Method 2: Using database MCP for data testing"Using PostgreSQL MCP, analyze the schema and suggest:- Constraint violation test cases- Transaction isolation tests needed- Performance test scenarios- Data integrity edge cases"
// Method 3: Manual implementation analysis@implementation/calculator.ts"Analyze this implementation and suggest:1. Missing test cases2. Potential edge cases3. Error conditions not handled4. Performance bottlenecks5. Precision issues"
Follow strict TDD cycle:
// 1. Write failing testit('should calculate tax-loss harvesting savings', () => { const result = calculateTaxLossHarvesting({ losses: 5000, gains: 8000, taxRate: 0.32, }); expect(result.taxSavings).toBe(960); // (5000 - 3000) * 0.32});
// 2. See it fail// ❌ calculateTaxLossHarvesting is not defined
// 3. Implement minimum to passfunction calculateTaxLossHarvesting({ losses, gains, taxRate }) { const netLoss = Math.min(losses, gains); return { taxSavings: netLoss * taxRate };}
// 4. Refactor with confidence
Ensure test quality:
// Configure Stryker for mutation testing{ "mutator": { "name": "typescript", "excludedMutations": ["StringLiteral"] }, "testRunner": "jest", "coverageAnalysis": "perTest", "thresholds": { "high": 90, "low": 85, "break": 80 }}
Problem: Tests break with minor refactoring
Solution:
// Bad: Testing implementation detailsit('should call Math.pow', () => { const spy = jest.spyOn(Math, 'pow'); calculateCompoundInterest({ ... }); expect(spy).toHaveBeenCalled();});
// Good: Testing behaviorit('should calculate correct result', () => { const result = calculateCompoundInterest({ ... }); expectFinanciallyEqual(result.finalAmount, 1628.89);});
Problem: Missing edge cases and error paths
Solution:
// Use AI to discover edge cases"What edge cases should I test for a function that:- Divides two numbers- Handles currency- Works with user input"
// AI suggests: zero division, precision,// overflow, NaN, negative values, etc.
Problem: Test suite takes too long
Solution:
// Parallelize independent testsdescribe.concurrent('Calculator Tests', () => { it.concurrent('test 1', async () => { ... }); it.concurrent('test 2', async () => { ... });});
// Mock expensive operationsjest.mock('./market-data-api');
Advanced testing techniques:
Contract Testing
Chaos Testing
Visual Testing
MCP-Powered Testing
Your TDD implementation succeeds when:
Teams using AI-assisted TDD report:
Before writing implementation:
You’ve mastered TDD with AI. Ready for more?
Mutation Testing
Set up advanced mutation testing
Test Automation
Build AI-powered test generation
Performance Testing
Create comprehensive performance suite
Continue to Performance Optimization →