liveBook · Manning

Dependency injection

As explained in The Art of Unit Testing 3e

A technique in which a component receives a value or behavior from outside rather than creating it internally.

Dependency injection means giving a component the value, function, or object it needs from the outside instead of having it create or import that dependency itself. The immediate payoff is control: a password verifier can receive a chosen day rather than reading the real current day, making unusual cases predictable to test. Passing all dependencies in this way also makes a function pure when it has no side effects. section 3.3.1

Injection creates a seam: a place where a real dependency can later be replaced by a fake. A direct module import has no such replacement point unless the design changes. This is not limited to complex objects—an injected integer, function, or object can be a stub when the test controls its value or behavior. section 3.3.1 section 3.5

Choose an injection shape that fits the dependency and design:

  • Pass a value when the dependency is simple state, such as the current day. section 3.3.1
  • Pass a function when behavior must be supplied or special results and exceptions must be simulated. section 3.4.1
  • Use partial application or a factory function to preconfigure dependencies and return a smaller function for normal use. section 3.4.2
  • Pass an object through a constructor when an object-oriented design needs a collaborator such as a time provider. section 3.7.2
  • Wrap imported modules in a dependency object only when module-level replacement is needed; provide a reset after tests replace the real dependency. section 3.5

Testing a weekend without waiting

A verifier that reads the clock internally cannot reliably reach a weekend case on demand. Instead, give it getDayFn and pass an alwaysSunday function in the test. The verifier then throws for Sunday every time. The same idea works with a FakeTimeProvider object whose getDay method returns Sunday. section 3.4.1 section 3.7.2

Manual injection is often enough. Factory functions can keep production construction in one place while tests construct objects directly with fakes. Framework-managed IoC or DI containers can configure constructor injection, but they are not required for effective tests and may add details that obscure what a test is setting up. section 3.7.2

Injection supports both stubs and mocks, but their testing purposes differ. A stub supplies controlled values or failures; a mock can verify an interaction, such as whether a logger received “PASSED.” In either case, the injected replacement only needs the part of the dependency interface that the component uses. section 3.7.3 section 4.3

Listing 3.6 Using a higher-order factory function
const SUNDAY = 0, . . . FRIDAY=5, SATURDAY = 6;
 
const makeVerifier = (rules, dayOfWeekFn) => {
    return function (input) {
        if ([SATURDAY, SUNDAY].includes(dayOfWeekFn())) {
            throw new Error("It's the weekend!");
        }
        //more code goes here..
    };
};
 
describe('verifier', () => {
    test('factory method: on weekends, throws exceptions', () => {
        const alwaysSunday = () => SUNDAY;
        const verifyPassword = makeVerifier([], alwaysSunday);
 
        expect(() => verifyPassword('anything'))
            .toThrow("It's the weekend!");
    });

A factory function shows dependency injection through partial application: rules and a day function are configured once, then the returned verifier accepts only input. section 3.4.2

How The Art of Unit Testing 3e covers it

How deep it goes
Works through value, function, factory, module, constructor, and interface-based injection, while deferring broader design choices.
Examples
Uses a password verifier with weekend dates, fake time providers, a fake moment.js API, and an injected logger.
What it assumes
Introduces manual injection from simple parameters without requiring prior framework or container knowledge.
What you can do after
Apply it via parameters, factories, constructors, or module wrappers to make tests repeatable and controlled.
How it connects
Connects dependency injection with stubs, mock objects, pure functions, partial application, duck typing, interfaces, and IoC containers.

Also explained in

Compare how 21 books explain dependency injection →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of The Art of Unit Testing 3e. The section links point to the parts of the book it draws on.