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() or Math.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.

Taught in these lessons

Related reference

Updated 2026-09-03