Module injection replaces a module’s dependency with a fake through a public module boundary. The fake keeps the original API while making tests more reliable and controllable. section 3.3 section 3.8
A module typically keeps original dependencies separately, routes calls through a replaceable dependency object, and exposes injection and reset functions. Tests inject a fake, exercise the module, then restore the originals. section 3.5 section 4.5.2
Faking a time dependency
A password verifier can depend on moment.js for the current day. A test injects a replacement whose day function returns Saturday, verifies the weekend error, and calls reset afterward. section 3.5 section 4.5
Listing 3.7 Abstracting the required dependencies
const originalDependencies = { #1
moment: require(‘moment’), #1
}; #1
let dependencies = { ...originalDependencies }; #2
const inject = (fakes) => { #3
Object.assign(dependencies, fakes);
return function reset() { #4
dependencies = { ...originalDependencies };
}
};
const SUNDAY = 0; const SATURDAY = 6;
const verifyPassword = (input, rules) => {
const dayOfWeek = dependencies.moment().day();
if ([SATURDAY, SUNDAY].includes(dayOfWeek)) {
throw Error("It's the weekend!");
}
// more code goes here...
// return list of errors found..
return [];
};
module.exports = {
SATURDAY,
verifyPassword,
inject
};
#1 Wrapping moment.js with an intermediary object
#2 The object containing the current dependency, either real or fake
#3 A function that replaces the real dependency with a fake one
#4 A function that resets the dependency back to the real one
Shows the dependency object, injection function, reset closure, and production code using the replaceable module. section 3.5
Listing 4.6 Testing with modular injection
const {
verifyPassword,
injectDependencies,
resetDependencies,
} = require("./password-verifier-injectable");
describe("password verifier", () => {
afterEach(resetDependencies);
describe("given logger and passing scenario", () => {
it("calls the logger with PASS", () => {
let logged = "";
const mockLog = { info: (text) => (logged = text) };
injectDependencies({ log: mockLog });
verifyPassword("anything", []);
expect(logged).toMatch(/PASSED/);
});
});
});
Shows a logger fake injected into a password verifier and reset automatically after each test. section 4.5.3
Weigh the technique before adopting it:
- Prefer controlled internal abstractions when third-party APIs might change; direct fakes can make many tests brittle. section 3.5
- Reset injected dependencies after every test to prevent state leaking between tests. section 4.5.3
- Choose function parameters, currying, factory functions, or constructor injection when exposing module injection is unsuitable. section 3.5 section 3.8
