Dependency Inversion
Depending on a passed-in interface rather than a concrete, hard-coded implementation — what makes substituting a test double possible.
Purpose
Dependency inversion means a function or module depends on a passed-in interface (a parameter, an injected object) rather than reaching directly for a concrete, hard-coded implementation. This is what makes substituting a test double — or swapping a real implementation for another — possible.
Example
// Tightly coupled — reaches directly for fetch, hard to test without a real network
async function getUserName(userId) {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
return user.name;
}
// Inverted — the fetching mechanism is a parameter, defaulting to the real one
async function getUserNameOf(userId, fetchUser = defaultFetchUser) {
const user = await fetchUser(userId);
return user.name;
}
Behavior notes
- A sensible default parameter (the real implementation) means calling code doesn’t need to change at all — only a test needs to supply something different.
- The same technique fixes hidden non-determinism from a clock or randomness — recall Testing and
Maintainability — by making
Date.now()orMath.random()an explicit parameter instead of an ambient global reached for directly. - This is a lighter-weight, JavaScript-idiomatic version of what dependency-injection frameworks in other ecosystems formalize with containers and decorators — the underlying idea (depend on an interface, not a concrete implementation) is the same.
Common mistakes
- Applying dependency inversion everywhere indiscriminately, adding parameters and indirection to code that will never realistically need a substitute — reserve it for genuine I/O, ambient state, or other real testing/flexibility boundaries.
- Inverting a dependency but giving it a non-obvious or missing default, making every call site responsible for supplying something that’s almost always the same real implementation.
Runtime notes
A design principle, not a language feature — applies identically regardless of framework or runtime.