Challenge 1: Refactor to Constructor Injection — Possible Solution ==================================================================== BEFORE (tightly coupled): class ReportGenerator { private logger = new ConsoleLogger(); private http = new HttpClient(); async fetchAndLog(url: string): Promise { this.logger.info(`Fetching ${url}`); const data = await this.http.get(url); this.logger.info(`Received ${data.length} bytes`); } } AFTER (constructor injection against interfaces): interface Logger { info(message: string): void; } interface HttpClient { get(url: string): Promise; } class ConsoleLogger implements Logger { info(message: string): void { console.log(`[INFO] ${message}`); } } class FetchHttpClient implements HttpClient { async get(url: string): Promise { const res = await fetch(url); return res.text(); } } class ReportGenerator { constructor( private logger: Logger, private http: HttpClient ) {} async fetchAndLog(url: string): Promise { this.logger.info(`Fetching ${url}`); const data = await this.http.get(url); this.logger.info(`Received ${data.length} bytes`); } } // Composition root const generator = new ReportGenerator(new ConsoleLogger(), new FetchHttpClient()); // In a test, swap in fakes with zero real I/O: class FakeLogger implements Logger { messages: string[] = []; info(message: string): void { this.messages.push(message); } } class FakeHttpClient implements HttpClient { async get(): Promise { return "mock data"; } } const testGenerator = new ReportGenerator(new FakeLogger(), new FakeHttpClient()); WHY THIS WORKS -------------- - ReportGenerator no longer imports or names any concrete class — only the Logger and HttpClient interfaces. - The composition root (wherever the app actually starts) is the only place that knows about ConsoleLogger and FetchHttpClient. - Tests can pass fakes with no network calls, no console noise, and full control over the fake's behavior.