The Coupling Problem
If a UserManager class initializes a connection database directly inside its constructor, they are tightly coupled. You cannot test UserManager behaviors without a live database running, and changing database drivers requires modifying UserManager code.
Inverting Control
Dependency Injection (DI) passes (injects) dependencies into the object from the outside (usually via the constructor). The UserManager simply expects an object matching a database interface, leaving the orchestration to the entry point or a DI container.
// Tightly coupled:
class BadService {
db = new PostgresDB();
}
// Dependency injected:
class GoodService {
constructor(private db: DBInterface) {}
}Increasing Testability
By using DI, writing unit tests becomes trivial. You can pass a mock database object that stores records in-memory, allowing you to test UserManager validations in milliseconds without running a database.
