An abstraction hides a dependency's detailed API behind a smaller interface that your code controls. Instead of importing a third-party logger or date library everywhere, centralize that dependency behind an adapter. This limits the damage when the external API changes and keeps tests focused on your own interface. section 5.2.2 section 3.5
Prefer an abstraction when direct dependencies would spread implementation details across the codebase:
- Direct imports couple production code and tests to a third-party API, so an upgrade can require changing many files. section 5.2.2
- An internal API provides a stable boundary: its implementation can change while callers and tests remain unchanged. section 3.5
- Do not confuse abstraction level with test type; unit and component tests are classified by their dependencies, not by how high-level they appear. section 10.1.2
Wrapping a date dependency
A password verifier can depend on a controlled `dependencies.moment` interface instead of importing `moment` directly. Tests then inject a tiny fake whose `day()` method returns Saturday, verify the weekend error, and call the returned reset function afterward. The fake models only the behavior the verifier needs. section 3.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 abstraction boundary around `moment`, including dependency storage, replacement through `inject`, and restoration through `reset`. section 3.5
Listing 3.8 Injecting a fake module with inject()
const { inject, verifyPassword, SATURDAY } = require('./password-verifier-time00-modular');
const injectDate = (newDay) => { #1
const reset = inject({ #2
moment: function () {
//we're faking the moment.js module's API here.
return {
day: () => newDay
}
}
});
return reset;
};
describe('verifyPassword', () => {
describe('when its the weekend', () => {
it('throws an error', () => {
const reset = injectDate(SATURDAY); #3
expect(() => verifyPassword('any input'))
.toThrow("It's the weekend!");
reset(); #4
});
});
});
#1 A helper function
#2 Injecting a fake API instead of moment.js
#3 Providing a fake day
#4 Resetting the dependency
Shows a test injecting a fake date API rather than faking the entire `moment.js` module, then restoring the original dependency. section 3.5














