Functions are created using a variant of Dependency Injection.
Any method with dependencies that should be stubbed out during tests, such as file system bindings, logging, or other functions,
takes in a first argument of name dependencies
.
Its dependencies object is manually created in src/cli/main.ts
and bound to the method with bind
.
Most functions don't need a dependencies
object.
Only add one if something should be stubbed out during tests or should be available to multiple callers.
Suppose your method myMethod
, should take in a fileSystem
, a string
, and a number
:
-
Create a
MyMethodDependencies
type inmyMethod.ts
:// ~~~/myMethod.ts export type MyMethodDependencies = { fileSystem: FileSystem; };
-
Add
dependencies: MyMethodDependencies
as the first argument tomyMethod
:// ~~~/myMethod.ts export const myMethod = async ( dependencies: MyMethodDependencies, argumentOne: string, argumentTwo: number, ) => { // ... };
-
In
src/cli/main.ts
, create amyMethodDependencies: MyMethodDependencies
:// src/cli/main.ts const myMethodDependencies: MyMethodDependencies = { fileSystem, };
-
In
src/cli/main.ts
, includemyMethod: bind(mymethod, myMethodDependencies)
in any dependencies object that requiresmyMethod
:// src/cli/main.ts const otherMethodDependencies: OtherMethodDependencies = { myMethod: bind(myMethod, myMethodDependencies), };
-
In the types of any dependencies that include
myMethod
, addmyMethod: SansDependencies<typeof myMethod>
to require the result ofbind
ingmyMethod
:// ~~~/otherMethod.ts export type OtherMethodDependencies = { myMethod: SansDependencies<typeof myMethod>; };
Global Node constructs as console
are never written to directly by functions; instead, "adapter" wrappers are set up in src/adapters/*.ts
and provided as dependencies to functions.
This enables native calls to be directly tested in tests without stubbing out their global equivalents.