A class is wrapped in Singleton machinery (`getInstance`, private constructor, static cache) but the singleton-ness is not load-bearing. Callers reach into the global accessor; tests can't isolate the class because the singleton instance leaks across test cases; dependencies are invisible from constructor signatures.
The class is a regular collaborator that callers receive through their constructors (or another explicit hand-off). Tests construct fresh instances per case; dependencies are visible at the type level; global state shrinks.
Before the refactoring
// Singleton machinery for what is effectively a regular collaborator.class Logger {static instance = null;static getInstance() {if (!Logger.instance) {Logger.instance = new Logger();}return Logger.instance;}constructor() {this.entries = [];}log(message) {this.entries.push({ time: Date.now(), message });}}// Client code reaches into the global accessor.function processOrder(order) {Logger.getInstance().log(`Processing order ${order.id}`);// ...}
After the refactoring
// Regular class; callers receive a logger through their constructor.class Logger {constructor() {this.entries = [];}log(message) {this.entries.push({ time: Date.now(), message });}}class OrderProcessor {constructor(logger) {this.logger = logger;}processOrder(order) {this.logger.log(`Processing order ${order.id}`);// ...}}
Singletons hide dependencies and leak state across tests. The static cache survives between test cases, producing flaky failures that depend on test ordering. New callers couple to the global accessor instead of declaring what they need.
Inlining a Singleton pushes dependency wiring outward — every caller now needs to know how to obtain the collaborator. Without a composition root, the wiring can scatter; reasonable Singletons (true cross-cutting infrastructure like a thread-safe metrics registry) may legitimately want the global access pattern.
Tests construct the class with stub collaborators; production wiring lives at the composition root; the dependency graph reads from constructor signatures. The static-cache class of flakiness disappears.
Inlining a Singleton without setting up a composition root leaves every caller responsible for constructing or finding the collaborator. The result can be worse than the Singleton — dozens of `new Logger()` instances each with their own state, accidentally fragmenting what was conceptually one log.