solid_testing_strategies 30 Q&As

Solid Testing Strategies FAQ & Answers

30 expert Solid Testing Strategies answers researched from official documentation. Every answer cites authoritative sources you can verify.

unknown

30 questions
A

Test SRP compliance via behavioral and structural indicators. Behavioral tests: (1) Count test file responsibilities - if testing UserService requires mocking database, email, validation, caching, logging = SRP violation (5 responsibilities). SRP compliant: Only mock user repository. (2) Change impact analysis - modify feature, count test files needing updates. >5 files = scattered responsibility. (3) Test setup complexity - if beforeEach() has 20 lines of mocks = too many dependencies. Structural tests: (1) Static analysis - class >500 lines, >20 methods flags SRP risk. (2) Import analysis - class importing from 5+ different domains (db, email, http, cache, crypto) = multiple responsibilities. (3) Method cohesion - LCOM >0.8 indicates low cohesion. Test pattern: describe('UserService', () => {const mockRepo = {save: jest.fn(), find: jest.fn()}; const service = new UserService(mockRepo); test('creates user', async () => {await service.create(userData); expect(mockRepo.save).toHaveBeenCalled();}); }). If test requires 10 mocks, extract responsibilities. Metrics: Cyclomatic complexity per method (<10), class size (<300 lines), number of dependencies (<5). Tools: SonarQube, CodeClimate flag SRP violations. TDD approach: Write test first - if hard to test, likely violates SRP.

99% confidence
A

Verify OCP via extension tests - add new behavior without modifying existing code or tests. Test pattern: Base tests remain unchanged when adding variants. Example: describe('PaymentProcessor', () => {testProcessor(new CreditCardProcessor()); testProcessor(new PayPalProcessor()); testProcessor(new BitcoinProcessor());}); function testProcessor(processor: PaymentProcessor) {test('processes valid payment', () => {expect(processor.process(100)).resolves.toBe(true);}); test('rejects invalid amount', () => {expect(processor.process(-1)).rejects.toThrow();});}}. OCP compliance: Adding BitcoinProcessor doesn't modify testProcessor() function or existing tests. Violations to detect: (1) Adding variant requires modifying existing test suite. (2) Tests with if(processor.type === 'credit') conditionals. (3) Tight coupling - tests know concrete implementations. Refactoring test indicator: Count modified lines when adding feature. OCP compliant: Add new test file for variant, zero lines changed in existing tests. Metrics: Test churn - files modified per feature. High churn = OCP violation. Modern: Parameterized tests (Jest describe.each, Vitest test.each) enable testing all variants with single test definition. Coverage: Each implementation has own test file + shared interface tests. Goal: Add BitcoinProcessor.test.ts, existing CreditCard.test.ts unchanged.

99% confidence
A

LSP testing: Write tests against base class, verify all subclasses pass identical tests without modification. Pattern: describe('Storage interface', () => {testStorage(new LocalStorage()); testStorage(new S3Storage()); testStorage(new MemoryStorage());}); function testStorage(storage: IStorage) {test('saves and retrieves', async () => {await storage.save('key', 'value'); const result = await storage.get('key'); expect(result).toBe('value');}); test('returns null for missing key', async () => {expect(await storage.get('nonexistent')).toBeNull();}); test('overwrites existing key', async () => {await storage.save('key', 'v1'); await storage.save('key', 'v2'); expect(await storage.get('key')).toBe('v2');});}}. LSP compliance: All storage implementations pass testStorage() without special cases. Violations: (1) if(storage instanceof S3Storage) skip test - S3Storage doesn't honor contract. (2) Different error types - S3 throws AwsError, Local throws FileError. (3) Different return types - S3 returns Buffer, Local returns string. Refactoring: Fix implementations to match contract. Benefits: Substitutability verified, behavioral compatibility guaranteed, safe polymorphism. Modern: Property-based testing (fast-check) generates random inputs, tests all implementations with same property. Coverage: 100% of base interface methods tested with all implementations.

99% confidence
A

Test ISP via mock analysis - count mocked methods vs used methods. Pattern: test('OrderService processes order', () => {const mockPayment = {process: jest.fn().mockResolvedValue(true), refund: jest.fn(), generateReport: jest.fn(), auditLog: jest.fn(), updateSettings: jest.fn()}; const service = new OrderService(mockPayment); service.processOrder(order); expect(mockPayment.process).toHaveBeenCalled(); // Only process() used, 4 methods stubbed unnecessarily}). ISP violation: OrderService depends on PaymentProcessor interface with 5 methods but uses only 1. Refactoring: Extract focused interface. interface PaymentExecutor {process(amount): Promise}. Test becomes: const mockPayment: PaymentExecutor = {process: jest.fn()}; const service = new OrderService(mockPayment). Benefits: Minimal mocks, clear dependencies, easier testing. Metrics: Mock efficiency = used methods / total methods. <50% = ISP violation. Detection: (1) Test has jest.fn() for methods never verified. (2) Lots of unused mocks (dead code in tests). (3) Comments like // not used but required by interface. Modern: TypeScript Pick<Interface, 'method1' | 'method2'> creates focused test interface. Tools: Coverage reports show mocked but never called methods. Best practice: Test should mock only what component uses. If mocking 10 methods but using 2, extract smaller interface.

99% confidence
A

Test DIP via dependency injection and test doubles - if can't inject mocks, violates DIP. Pattern: class OrderService {constructor(private db: IDatabase, private email: IEmailSender) {}}. Test: const mockDb: IDatabase = {save: jest.fn(), find: jest.fn()}; const mockEmail: IEmailSender = {send: jest.fn()}; const service = new OrderService(mockDb, mockEmail); await service.processOrder(order); expect(mockDb.save).toHaveBeenCalledWith(order); expect(mockEmail.send).toHaveBeenCalled(). DIP compliance: OrderService testable with mocks, no real database/email needed. Violations: (1) Can't test without real dependencies - class OrderService {private db = new MySQLDatabase()} forces integration test. (2) Hard to mock - Service Locator pattern requires global setup. (3) Concrete dependencies - new CreditCardProcessor() in code, can't swap. Test doubles: (1) Mock - verify interactions (expect(db.save).toHaveBeenCalled()). (2) Stub - provide canned responses (jest.fn().mockResolvedValue(user)). (3) Fake - working implementation (InMemoryDatabase for tests). (4) Spy - wrap real object to verify calls. Modern: Jest/Vitest automatic mocks, TypeScript jest.Mocked for type-safe mocks. Benefits: Fast tests (no I/O), isolated tests (no side effects), deterministic tests (no flaky network). Metric: Test speed - if >100ms, likely hitting real dependencies (DIP violation).

99% confidence
A

Contract tests verify interface contracts are honored by all implementations - ensures LSP and ISP compliance. Pattern: interface IPaymentGateway {charge(amount: number): Promise; refund(transactionId: string): Promise;}. Contract test suite: export function testPaymentGateway(createGateway: () => IPaymentGateway) {describe('PaymentGateway contract', () => {let gateway: IPaymentGateway; beforeEach(() => {gateway = createGateway();}); test('charge returns receipt with transaction ID', async () => {const receipt = await gateway.charge(100); expect(receipt).toHaveProperty('transactionId'); expect(typeof receipt.transactionId).toBe('string');}); test('refund succeeds for valid transaction', async () => {const receipt = await gateway.charge(100); await expect(gateway.refund(receipt.transactionId)).resolves.not.toThrow();}); test('charge rejects negative amounts', async () => {await expect(gateway.charge(-50)).rejects.toThrow();});})}. Usage: testPaymentGateway(() => new StripeGateway(apiKey)); testPaymentGateway(() => new PayPalGateway(config)). Benefits: All implementations verified against contract, LSP compliance automated, consistent behavior guaranteed. Modern: Pact (consumer-driven contracts), OpenAPI contracts for HTTP APIs. Coverage: Test all interface methods, all edge cases, all error conditions. If implementation fails contract test = LSP violation.

99% confidence
A

Mutation testing verifies test quality by introducing bugs (mutants) - helps detect untested SOLID violations. Process: (1) Tool mutates code (change > to >=, remove if condition, swap return values). (2) Run test suite against mutant. (3) If tests pass = escaped mutant = weak tests. Goal: Kill all mutants (tests fail for mutations). SOLID connection: Well-designed code (SOLID) easier to mutation test. SRP: Small focused classes have focused tests, easier to kill mutants. Example: class Validator {validate(email: string) {return /\S+@\S+/.test(email);}}. Mutation: return true (remove regex). Test: expect(validator.validate('invalid')).toBe(false) kills mutant. OCP: Mutation tests verify all implementations. Mutate StripeProcessor.charge(), tests for PayPal should pass (isolated), tests for Stripe should fail (specific). Tools: Stryker (JavaScript/TypeScript), PITest (Java). Configuration: stryker init, stryker run. Metrics: Mutation score = killed mutants / total mutants. Target: >80%. Benefits: Finds weak tests (assertions missing), verifies code actually tested (not just covered), improves test quality. Cost: Slow (runs tests N times for N mutants). Use on critical paths. Modern: Differential mutation testing (mutate only changed code), parallel execution (faster).

99% confidence
A

TDD enforces SRP by making design pain obvious during test writing - hard to test = SRP violation. Red-Green-Refactor with SRP: (1) Red - Write test for single responsibility. test('UserService creates user', () => {const service = new UserService(mockRepo); service.create(user); expect(mockRepo.save).toHaveBeenCalled();}). (2) Green - Implement minimum code to pass. class UserService {constructor(private repo: UserRepository) {} create(user: User) {this.repo.save(user);}}. (3) Refactor - Extract additional responsibilities. If service.create() also validates, sends email, logs - extract to separate classes. TDD signals for SRP violations: (1) Test setup is complex (10+ mocks needed). (2) Testing one method requires testing unrelated concerns. (3) Mockito/Jest mock hell (mocking mocks). (4) Can't name test clearly ('creates user and sends email and logs' = multiple responsibilities). Refactoring: test('UserService creates user', ...); test('EmailService sends welcome email', ...); test('Logger logs user creation', ...). Benefits: SRP violations caught at test-writing time (before implementation), forces thinking about responsibilities upfront, results in testable code. Modern: Outside-in TDD (start with acceptance test, drill down to unit tests) naturally creates focused responsibilities. Coverage: One test file per responsibility.

99% confidence
A

Characterization tests document existing behavior before refactoring - safety net for SOLID improvements to legacy code. Process: (1) Write tests describing current behavior (even if buggy). (2) Run tests, verify they pass. (3) Refactor towards SOLID. (4) Tests ensure behavior unchanged. Pattern: test('legacy UserService behavior', () => {const service = new UserService(); const result = service.processUser({name: 'Alice', email: '[email protected]'}); expect(result).toEqual({id: expect.any(Number), name: 'ALICE', email: '[email protected]', createdAt: expect.any(Date), welcomeEmailSent: true});}). Test captures current behavior (uppercases name, generates ID, sends email). Refactoring: Extract EmailService, IDGenerator, NameFormatter. Tests still pass = behavior preserved. Benefits: Safe refactoring (tests catch regressions), documents legacy behavior (even bugs), enables SOLID migration (can refactor without breaking). Michael Feathers: 'Characterization tests describe what system actually does, not what it should do.' Approval testing: Record output as approved snapshot, verify future runs match. Tools: Jest snapshots, ApprovalTests library. Strategy: Write characterization tests for hot paths (frequently changed code), refactor towards SOLID (extract responsibilities), gradually replace characterization tests with focused unit tests. Modern: Snapshot testing (React components), golden file testing (CLI output).

99% confidence
A

Use differential coverage to detect OCP violations - adding feature should add tests, not modify existing test coverage. Process: (1) Baseline - measure code coverage before feature. (2) Add feature (new payment method). (3) Measure coverage again. (4) Analyze changes. OCP compliant: Coverage added in new files only (BitcoinProcessor.test.ts), existing files unchanged (PaymentService.test.ts). Violation: Existing test files modified to handle new case (if(type === 'bitcoin') in PaymentService.test.ts). Tools: Istanbul/nyc (JavaScript coverage), Codecov (track coverage over time), SonarQube (differential coverage reports). Metrics: (1) Coverage churn - % of existing coverage modified. OCP: <5%. (2) New vs modified lines - OCP: 90% new, 10% modified. (3) Test file edits - adding feature should create new test files, not edit existing. Example report: + BitcoinProcessor.test.ts (120 lines, 100% coverage). PaymentService.test.ts (unchanged). PaymentProcessor.test.ts (added 1 line for new type in shared test). Benefits: Quantifies OCP compliance, tracks architectural decay over time, enforces extensibility. Modern: GitHub/GitLab CI checks enforce coverage rules (no decrease in existing files, only additions). Pre-commit hooks: Fail if modifying >5% existing test lines for new feature.

99% confidence
A

Property-based testing generates random inputs to verify invariants - useful for testing LSP and interface contracts. Pattern: import {fc, test} from 'fast-check'; test('all Storage implementations preserve data', () => {fc.assert(fc.property(fc.string(), fc.string(), async (key, value) => {const storage = new LocalStorage(); await storage.save(key, value); const retrieved = await storage.get(key); expect(retrieved).toBe(value);}));}). Fast-check generates 100 random key/value pairs, verifies property holds for all. LSP testing: test('all implementations', () => {[LocalStorage, S3Storage, MemoryStorage].forEach(StorageClass => {fc.assert(fc.property(fc.string(), fc.string(), async (key, value) => {const storage = new StorageClass(); await storage.save(key, value); expect(await storage.get(key)).toBe(value);}));});}). Benefits: Tests edge cases (empty strings, special characters, very long values) automatically, finds bugs example-based tests miss, verifies contracts hold for all inputs. SOLID connection: LSP - verify all implementations satisfy same properties. ISP - property tests focus on minimal interface (only methods actually tested). OCP - adding implementation doesn't require new test cases (property tests apply automatically). Modern: Hypothesis (Python), QuickCheck (Haskell), fast-check (JavaScript). Strategies: Shrinking (find minimal failing case), custom generators (domain-specific values), statistical testing (distribution of generated values).

99% confidence
A

Architectural tests verify code structure and dependencies - automate SOLID rules as executable tests. Pattern: import {ArchUnit} from 'ts-arch'; test('Domain layer has no infrastructure dependencies', () => {const rule = filesOfPackage('domain').shouldNotDependOn('infrastructure'); expect(rule.check()).toPass();}). Tests enforce: (1) DIP - High-level modules don't depend on low-level. test('services depend on interfaces', () => {filesOfPackage('services').shouldDependOnlyOn('interfaces', 'domain');}). (2) SRP - Classes in layer have consistent dependencies. test('controllers depend only on use cases', () => {filesOfPackage('controllers').shouldDependOnlyOn('use-cases');}). (3) OCP - New features added without modifying core. test('core has no dependencies on features', () => {filesOfPackage('core').shouldNotDependOn('features');}). Tools: ArchUnit (Java), ts-arch (TypeScript), dependency-cruiser (JavaScript). Rules: No cycles, layer dependencies (UI → Domain → Infrastructure never reverse), naming conventions (services end with Service). Benefits: Architecture as code (rules executable, not just documentation), prevents drift (tests fail if architecture violated), onboarding (new devs see rules in tests). Modern: ESLint plugins (no-restricted-imports), import linters, module boundaries in monorepos (Nx). Example: test('no circular dependencies', () => {expect(analyzeCycles(project)).toHaveLength(0);}).

99% confidence
A

Test DI container configuration to verify correct wiring and SOLID principles. Pattern: describe('DI Container', () => {let container: Container; beforeEach(() => {container = new Container(); registerServices(container);}); test('resolves UserService with dependencies', () => {const service = container.get('UserService'); expect(service).toBeInstanceOf(UserService); expect(service).toHaveProperty('repository'); expect(service).toHaveProperty('logger');}); test('singletons return same instance', () => {const service1 = container.get('Logger'); const service2 = container.get('Logger'); expect(service1).toBe(service2);}); test('transients return different instances', () => {const service1 = container.get('RequestHandler'); const service2 = container.get('RequestHandler'); expect(service1).not.toBe(service2);}); test('resolves all IPaymentProcessor implementations', () => {const processors = container.getAll('IPaymentProcessor'); expect(processors).toHaveLength(3); expect(processors.map(p => p.constructor.name)).toEqual(['StripeProcessor', 'PayPalProcessor', 'BitcoinProcessor']);}); test('throws for circular dependencies', () => {container.bind('ServiceA').to(ServiceA); container.bind('ServiceB').to(ServiceB); expect(() => container.get('ServiceA')).toThrow(/circular dependency/);}); }). Benefits: Catches wiring errors (typos in service names), verifies lifecycle (singleton vs transient), detects circular dependencies, ensures all dependencies resolvable. Modern: InversifyJS, TSyringe have built-in container tests.

99% confidence
A

Test brittleness: Tests break for unrelated changes - indicates tight coupling violating SOLID. Metrics: (1) Test churn rate - % of tests modified per code change. Brittle: >50%. SOLID: <20%. (2) Cascading failures - single code change breaks 10+ tests (tight coupling). (3) Test maintenance time - hours spent fixing tests vs implementing feature. Target: 1:2 ratio. Detection: test('creates user', () => {const db = new Database('localhost', 5432, 'prod', 'user', 'pass'); const email = new EmailService('smtp.gmail.com', 587, '[email protected]', 'pass'); const logger = new Logger('production', '/var/log'); const cache = new Cache('redis://localhost:6379'); const service = new UserService(db, email, logger, cache); service.create(user);}). Brittleness: Test knows concrete implementations (Database, EmailService), environment details (localhost, ports), configuration (production mode). Breaks when: DB host changes, email provider changes, log path changes, cache URL changes. SOLID solution: Mock interfaces - const mockDb: IDatabase = {save: jest.fn()}; const service = new UserService(mockDb). Benefits: Tests isolated (only break when UserService changes), fast (no real dependencies), flexible (swap implementations). Modern: Test builders, object mothers, factory functions reduce brittleness. Metric: Test pyramid - 70% unit (isolated), 20% integration, 10% E2E. Inverted pyramid = brittle tests.

99% confidence
A

Test React ISP via prop analysis - components should use all props received, not just subset. Pattern: test('Button uses all props', () => {const props = {onClick: jest.fn(), label: 'Click me', disabled: false, variant: 'primary'}; render(<Button {...props} />); const button = screen.getByRole('button'); expect(button).toHaveTextContent('Click me'); expect(button).toHaveClass('primary'); expect(button).not.toBeDisabled(); fireEvent.click(button); expect(props.onClick).toHaveBeenCalled();}). All props tested = no unused dependencies. ISP violation detection: test('Header receives user but only uses user.name', () => {const user = {name: 'Alice', email: '[email protected]', role: 'admin', createdAt: new Date()}; render(

); expect(screen.getByText('Alice')).toBeInTheDocument(); // Only name used, email/role/createdAt unused}). Refactoring: interface HeaderProps {userName: string}.
. Benefits: Minimal props (easier testing), no over-fetching data, clearer component contracts. Modern: TypeScript Pick<User, 'name'> creates focused prop type. Testing-library queries show what component actually uses. Metric: Prop usage rate - used props / total props. Target: >90%. Tools: eslint-plugin-react checks for unused props. Component tests should verify all props affect render or behavior.

99% confidence
A

Test SRP compliance via behavioral and structural indicators. Behavioral tests: (1) Count test file responsibilities - if testing UserService requires mocking database, email, validation, caching, logging = SRP violation (5 responsibilities). SRP compliant: Only mock user repository. (2) Change impact analysis - modify feature, count test files needing updates. >5 files = scattered responsibility. (3) Test setup complexity - if beforeEach() has 20 lines of mocks = too many dependencies. Structural tests: (1) Static analysis - class >500 lines, >20 methods flags SRP risk. (2) Import analysis - class importing from 5+ different domains (db, email, http, cache, crypto) = multiple responsibilities. (3) Method cohesion - LCOM >0.8 indicates low cohesion. Test pattern: describe('UserService', () => {const mockRepo = {save: jest.fn(), find: jest.fn()}; const service = new UserService(mockRepo); test('creates user', async () => {await service.create(userData); expect(mockRepo.save).toHaveBeenCalled();}); }). If test requires 10 mocks, extract responsibilities. Metrics: Cyclomatic complexity per method (<10), class size (<300 lines), number of dependencies (<5). Tools: SonarQube, CodeClimate flag SRP violations. TDD approach: Write test first - if hard to test, likely violates SRP.

99% confidence
A

Verify OCP via extension tests - add new behavior without modifying existing code or tests. Test pattern: Base tests remain unchanged when adding variants. Example: describe('PaymentProcessor', () => {testProcessor(new CreditCardProcessor()); testProcessor(new PayPalProcessor()); testProcessor(new BitcoinProcessor());}); function testProcessor(processor: PaymentProcessor) {test('processes valid payment', () => {expect(processor.process(100)).resolves.toBe(true);}); test('rejects invalid amount', () => {expect(processor.process(-1)).rejects.toThrow();});}}. OCP compliance: Adding BitcoinProcessor doesn't modify testProcessor() function or existing tests. Violations to detect: (1) Adding variant requires modifying existing test suite. (2) Tests with if(processor.type === 'credit') conditionals. (3) Tight coupling - tests know concrete implementations. Refactoring test indicator: Count modified lines when adding feature. OCP compliant: Add new test file for variant, zero lines changed in existing tests. Metrics: Test churn - files modified per feature. High churn = OCP violation. Modern: Parameterized tests (Jest describe.each, Vitest test.each) enable testing all variants with single test definition. Coverage: Each implementation has own test file + shared interface tests. Goal: Add BitcoinProcessor.test.ts, existing CreditCard.test.ts unchanged.

99% confidence
A

LSP testing: Write tests against base class, verify all subclasses pass identical tests without modification. Pattern: describe('Storage interface', () => {testStorage(new LocalStorage()); testStorage(new S3Storage()); testStorage(new MemoryStorage());}); function testStorage(storage: IStorage) {test('saves and retrieves', async () => {await storage.save('key', 'value'); const result = await storage.get('key'); expect(result).toBe('value');}); test('returns null for missing key', async () => {expect(await storage.get('nonexistent')).toBeNull();}); test('overwrites existing key', async () => {await storage.save('key', 'v1'); await storage.save('key', 'v2'); expect(await storage.get('key')).toBe('v2');});}}. LSP compliance: All storage implementations pass testStorage() without special cases. Violations: (1) if(storage instanceof S3Storage) skip test - S3Storage doesn't honor contract. (2) Different error types - S3 throws AwsError, Local throws FileError. (3) Different return types - S3 returns Buffer, Local returns string. Refactoring: Fix implementations to match contract. Benefits: Substitutability verified, behavioral compatibility guaranteed, safe polymorphism. Modern: Property-based testing (fast-check) generates random inputs, tests all implementations with same property. Coverage: 100% of base interface methods tested with all implementations.

99% confidence
A

Test ISP via mock analysis - count mocked methods vs used methods. Pattern: test('OrderService processes order', () => {const mockPayment = {process: jest.fn().mockResolvedValue(true), refund: jest.fn(), generateReport: jest.fn(), auditLog: jest.fn(), updateSettings: jest.fn()}; const service = new OrderService(mockPayment); service.processOrder(order); expect(mockPayment.process).toHaveBeenCalled(); // Only process() used, 4 methods stubbed unnecessarily}). ISP violation: OrderService depends on PaymentProcessor interface with 5 methods but uses only 1. Refactoring: Extract focused interface. interface PaymentExecutor {process(amount): Promise}. Test becomes: const mockPayment: PaymentExecutor = {process: jest.fn()}; const service = new OrderService(mockPayment). Benefits: Minimal mocks, clear dependencies, easier testing. Metrics: Mock efficiency = used methods / total methods. <50% = ISP violation. Detection: (1) Test has jest.fn() for methods never verified. (2) Lots of unused mocks (dead code in tests). (3) Comments like // not used but required by interface. Modern: TypeScript Pick<Interface, 'method1' | 'method2'> creates focused test interface. Tools: Coverage reports show mocked but never called methods. Best practice: Test should mock only what component uses. If mocking 10 methods but using 2, extract smaller interface.

99% confidence
A

Test DIP via dependency injection and test doubles - if can't inject mocks, violates DIP. Pattern: class OrderService {constructor(private db: IDatabase, private email: IEmailSender) {}}. Test: const mockDb: IDatabase = {save: jest.fn(), find: jest.fn()}; const mockEmail: IEmailSender = {send: jest.fn()}; const service = new OrderService(mockDb, mockEmail); await service.processOrder(order); expect(mockDb.save).toHaveBeenCalledWith(order); expect(mockEmail.send).toHaveBeenCalled(). DIP compliance: OrderService testable with mocks, no real database/email needed. Violations: (1) Can't test without real dependencies - class OrderService {private db = new MySQLDatabase()} forces integration test. (2) Hard to mock - Service Locator pattern requires global setup. (3) Concrete dependencies - new CreditCardProcessor() in code, can't swap. Test doubles: (1) Mock - verify interactions (expect(db.save).toHaveBeenCalled()). (2) Stub - provide canned responses (jest.fn().mockResolvedValue(user)). (3) Fake - working implementation (InMemoryDatabase for tests). (4) Spy - wrap real object to verify calls. Modern: Jest/Vitest automatic mocks, TypeScript jest.Mocked for type-safe mocks. Benefits: Fast tests (no I/O), isolated tests (no side effects), deterministic tests (no flaky network). Metric: Test speed - if >100ms, likely hitting real dependencies (DIP violation).

99% confidence
A

Contract tests verify interface contracts are honored by all implementations - ensures LSP and ISP compliance. Pattern: interface IPaymentGateway {charge(amount: number): Promise; refund(transactionId: string): Promise;}. Contract test suite: export function testPaymentGateway(createGateway: () => IPaymentGateway) {describe('PaymentGateway contract', () => {let gateway: IPaymentGateway; beforeEach(() => {gateway = createGateway();}); test('charge returns receipt with transaction ID', async () => {const receipt = await gateway.charge(100); expect(receipt).toHaveProperty('transactionId'); expect(typeof receipt.transactionId).toBe('string');}); test('refund succeeds for valid transaction', async () => {const receipt = await gateway.charge(100); await expect(gateway.refund(receipt.transactionId)).resolves.not.toThrow();}); test('charge rejects negative amounts', async () => {await expect(gateway.charge(-50)).rejects.toThrow();});})}. Usage: testPaymentGateway(() => new StripeGateway(apiKey)); testPaymentGateway(() => new PayPalGateway(config)). Benefits: All implementations verified against contract, LSP compliance automated, consistent behavior guaranteed. Modern: Pact (consumer-driven contracts), OpenAPI contracts for HTTP APIs. Coverage: Test all interface methods, all edge cases, all error conditions. If implementation fails contract test = LSP violation.

99% confidence
A

Mutation testing verifies test quality by introducing bugs (mutants) - helps detect untested SOLID violations. Process: (1) Tool mutates code (change > to >=, remove if condition, swap return values). (2) Run test suite against mutant. (3) If tests pass = escaped mutant = weak tests. Goal: Kill all mutants (tests fail for mutations). SOLID connection: Well-designed code (SOLID) easier to mutation test. SRP: Small focused classes have focused tests, easier to kill mutants. Example: class Validator {validate(email: string) {return /\S+@\S+/.test(email);}}. Mutation: return true (remove regex). Test: expect(validator.validate('invalid')).toBe(false) kills mutant. OCP: Mutation tests verify all implementations. Mutate StripeProcessor.charge(), tests for PayPal should pass (isolated), tests for Stripe should fail (specific). Tools: Stryker (JavaScript/TypeScript), PITest (Java). Configuration: stryker init, stryker run. Metrics: Mutation score = killed mutants / total mutants. Target: >80%. Benefits: Finds weak tests (assertions missing), verifies code actually tested (not just covered), improves test quality. Cost: Slow (runs tests N times for N mutants). Use on critical paths. Modern: Differential mutation testing (mutate only changed code), parallel execution (faster).

99% confidence
A

TDD enforces SRP by making design pain obvious during test writing - hard to test = SRP violation. Red-Green-Refactor with SRP: (1) Red - Write test for single responsibility. test('UserService creates user', () => {const service = new UserService(mockRepo); service.create(user); expect(mockRepo.save).toHaveBeenCalled();}). (2) Green - Implement minimum code to pass. class UserService {constructor(private repo: UserRepository) {} create(user: User) {this.repo.save(user);}}. (3) Refactor - Extract additional responsibilities. If service.create() also validates, sends email, logs - extract to separate classes. TDD signals for SRP violations: (1) Test setup is complex (10+ mocks needed). (2) Testing one method requires testing unrelated concerns. (3) Mockito/Jest mock hell (mocking mocks). (4) Can't name test clearly ('creates user and sends email and logs' = multiple responsibilities). Refactoring: test('UserService creates user', ...); test('EmailService sends welcome email', ...); test('Logger logs user creation', ...). Benefits: SRP violations caught at test-writing time (before implementation), forces thinking about responsibilities upfront, results in testable code. Modern: Outside-in TDD (start with acceptance test, drill down to unit tests) naturally creates focused responsibilities. Coverage: One test file per responsibility.

99% confidence
A

Characterization tests document existing behavior before refactoring - safety net for SOLID improvements to legacy code. Process: (1) Write tests describing current behavior (even if buggy). (2) Run tests, verify they pass. (3) Refactor towards SOLID. (4) Tests ensure behavior unchanged. Pattern: test('legacy UserService behavior', () => {const service = new UserService(); const result = service.processUser({name: 'Alice', email: '[email protected]'}); expect(result).toEqual({id: expect.any(Number), name: 'ALICE', email: '[email protected]', createdAt: expect.any(Date), welcomeEmailSent: true});}). Test captures current behavior (uppercases name, generates ID, sends email). Refactoring: Extract EmailService, IDGenerator, NameFormatter. Tests still pass = behavior preserved. Benefits: Safe refactoring (tests catch regressions), documents legacy behavior (even bugs), enables SOLID migration (can refactor without breaking). Michael Feathers: 'Characterization tests describe what system actually does, not what it should do.' Approval testing: Record output as approved snapshot, verify future runs match. Tools: Jest snapshots, ApprovalTests library. Strategy: Write characterization tests for hot paths (frequently changed code), refactor towards SOLID (extract responsibilities), gradually replace characterization tests with focused unit tests. Modern: Snapshot testing (React components), golden file testing (CLI output).

99% confidence
A

Use differential coverage to detect OCP violations - adding feature should add tests, not modify existing test coverage. Process: (1) Baseline - measure code coverage before feature. (2) Add feature (new payment method). (3) Measure coverage again. (4) Analyze changes. OCP compliant: Coverage added in new files only (BitcoinProcessor.test.ts), existing files unchanged (PaymentService.test.ts). Violation: Existing test files modified to handle new case (if(type === 'bitcoin') in PaymentService.test.ts). Tools: Istanbul/nyc (JavaScript coverage), Codecov (track coverage over time), SonarQube (differential coverage reports). Metrics: (1) Coverage churn - % of existing coverage modified. OCP: <5%. (2) New vs modified lines - OCP: 90% new, 10% modified. (3) Test file edits - adding feature should create new test files, not edit existing. Example report: + BitcoinProcessor.test.ts (120 lines, 100% coverage). PaymentService.test.ts (unchanged). PaymentProcessor.test.ts (added 1 line for new type in shared test). Benefits: Quantifies OCP compliance, tracks architectural decay over time, enforces extensibility. Modern: GitHub/GitLab CI checks enforce coverage rules (no decrease in existing files, only additions). Pre-commit hooks: Fail if modifying >5% existing test lines for new feature.

99% confidence
A

Property-based testing generates random inputs to verify invariants - useful for testing LSP and interface contracts. Pattern: import {fc, test} from 'fast-check'; test('all Storage implementations preserve data', () => {fc.assert(fc.property(fc.string(), fc.string(), async (key, value) => {const storage = new LocalStorage(); await storage.save(key, value); const retrieved = await storage.get(key); expect(retrieved).toBe(value);}));}). Fast-check generates 100 random key/value pairs, verifies property holds for all. LSP testing: test('all implementations', () => {[LocalStorage, S3Storage, MemoryStorage].forEach(StorageClass => {fc.assert(fc.property(fc.string(), fc.string(), async (key, value) => {const storage = new StorageClass(); await storage.save(key, value); expect(await storage.get(key)).toBe(value);}));});}). Benefits: Tests edge cases (empty strings, special characters, very long values) automatically, finds bugs example-based tests miss, verifies contracts hold for all inputs. SOLID connection: LSP - verify all implementations satisfy same properties. ISP - property tests focus on minimal interface (only methods actually tested). OCP - adding implementation doesn't require new test cases (property tests apply automatically). Modern: Hypothesis (Python), QuickCheck (Haskell), fast-check (JavaScript). Strategies: Shrinking (find minimal failing case), custom generators (domain-specific values), statistical testing (distribution of generated values).

99% confidence
A

Architectural tests verify code structure and dependencies - automate SOLID rules as executable tests. Pattern: import {ArchUnit} from 'ts-arch'; test('Domain layer has no infrastructure dependencies', () => {const rule = filesOfPackage('domain').shouldNotDependOn('infrastructure'); expect(rule.check()).toPass();}). Tests enforce: (1) DIP - High-level modules don't depend on low-level. test('services depend on interfaces', () => {filesOfPackage('services').shouldDependOnlyOn('interfaces', 'domain');}). (2) SRP - Classes in layer have consistent dependencies. test('controllers depend only on use cases', () => {filesOfPackage('controllers').shouldDependOnlyOn('use-cases');}). (3) OCP - New features added without modifying core. test('core has no dependencies on features', () => {filesOfPackage('core').shouldNotDependOn('features');}). Tools: ArchUnit (Java), ts-arch (TypeScript), dependency-cruiser (JavaScript). Rules: No cycles, layer dependencies (UI → Domain → Infrastructure never reverse), naming conventions (services end with Service). Benefits: Architecture as code (rules executable, not just documentation), prevents drift (tests fail if architecture violated), onboarding (new devs see rules in tests). Modern: ESLint plugins (no-restricted-imports), import linters, module boundaries in monorepos (Nx). Example: test('no circular dependencies', () => {expect(analyzeCycles(project)).toHaveLength(0);}).

99% confidence
A

Test DI container configuration to verify correct wiring and SOLID principles. Pattern: describe('DI Container', () => {let container: Container; beforeEach(() => {container = new Container(); registerServices(container);}); test('resolves UserService with dependencies', () => {const service = container.get('UserService'); expect(service).toBeInstanceOf(UserService); expect(service).toHaveProperty('repository'); expect(service).toHaveProperty('logger');}); test('singletons return same instance', () => {const service1 = container.get('Logger'); const service2 = container.get('Logger'); expect(service1).toBe(service2);}); test('transients return different instances', () => {const service1 = container.get('RequestHandler'); const service2 = container.get('RequestHandler'); expect(service1).not.toBe(service2);}); test('resolves all IPaymentProcessor implementations', () => {const processors = container.getAll('IPaymentProcessor'); expect(processors).toHaveLength(3); expect(processors.map(p => p.constructor.name)).toEqual(['StripeProcessor', 'PayPalProcessor', 'BitcoinProcessor']);}); test('throws for circular dependencies', () => {container.bind('ServiceA').to(ServiceA); container.bind('ServiceB').to(ServiceB); expect(() => container.get('ServiceA')).toThrow(/circular dependency/);}); }). Benefits: Catches wiring errors (typos in service names), verifies lifecycle (singleton vs transient), detects circular dependencies, ensures all dependencies resolvable. Modern: InversifyJS, TSyringe have built-in container tests.

99% confidence
A

Test brittleness: Tests break for unrelated changes - indicates tight coupling violating SOLID. Metrics: (1) Test churn rate - % of tests modified per code change. Brittle: >50%. SOLID: <20%. (2) Cascading failures - single code change breaks 10+ tests (tight coupling). (3) Test maintenance time - hours spent fixing tests vs implementing feature. Target: 1:2 ratio. Detection: test('creates user', () => {const db = new Database('localhost', 5432, 'prod', 'user', 'pass'); const email = new EmailService('smtp.gmail.com', 587, '[email protected]', 'pass'); const logger = new Logger('production', '/var/log'); const cache = new Cache('redis://localhost:6379'); const service = new UserService(db, email, logger, cache); service.create(user);}). Brittleness: Test knows concrete implementations (Database, EmailService), environment details (localhost, ports), configuration (production mode). Breaks when: DB host changes, email provider changes, log path changes, cache URL changes. SOLID solution: Mock interfaces - const mockDb: IDatabase = {save: jest.fn()}; const service = new UserService(mockDb). Benefits: Tests isolated (only break when UserService changes), fast (no real dependencies), flexible (swap implementations). Modern: Test builders, object mothers, factory functions reduce brittleness. Metric: Test pyramid - 70% unit (isolated), 20% integration, 10% E2E. Inverted pyramid = brittle tests.

99% confidence
A

Test React ISP via prop analysis - components should use all props received, not just subset. Pattern: test('Button uses all props', () => {const props = {onClick: jest.fn(), label: 'Click me', disabled: false, variant: 'primary'}; render(<Button {...props} />); const button = screen.getByRole('button'); expect(button).toHaveTextContent('Click me'); expect(button).toHaveClass('primary'); expect(button).not.toBeDisabled(); fireEvent.click(button); expect(props.onClick).toHaveBeenCalled();}). All props tested = no unused dependencies. ISP violation detection: test('Header receives user but only uses user.name', () => {const user = {name: 'Alice', email: '[email protected]', role: 'admin', createdAt: new Date()}; render(

); expect(screen.getByText('Alice')).toBeInTheDocument(); // Only name used, email/role/createdAt unused}). Refactoring: interface HeaderProps {userName: string}.
. Benefits: Minimal props (easier testing), no over-fetching data, clearer component contracts. Modern: TypeScript Pick<User, 'name'> creates focused prop type. Testing-library queries show what component actually uses. Metric: Prop usage rate - used props / total props. Target: >90%. Tools: eslint-plugin-react checks for unused props. Component tests should verify all props affect render or behavior.

99% confidence