Handling InversifyJS Identifiers
InversifyJS has the concepts of different bindings and custom decorators that might be used while working with the DI container. This guide will walk through the extras that InversifyJS bring, and how Automock integrates to them.
Multi-Inject Tokens
For scenarios where multiple implementations of a dependency are required, InversifyJS provides the @multiInject
decorator.
Example:
interface Weapon { ... }
@injectable()
export class Samurai {
public constructor(@multiInject('Weapon') private weapons: Weapon[]) {}
}
@multiInject
decorator work exactly the same as the @inject
decorator, refer to
the Token-based injection
📚 Read more about multi-injection in the InversifyJS documentation.
Handling Injections with Metadata
Tagged Bindings
Tagged bindings are used to differentiate between multiple bindings of the same service. For example:
@injectable()
class Ninja {
public constructor(
@inject("Weapon") @tagged("canThrow", false) private katana: Weapon,
@inject("Weapon") @tagged("canThrow", true) private shuriken: Weapon
) {}
}
You can handle these bindings by specifying the injection token and the corresponding metadata like so:
unitRef.get('Weapon', {canThrow: false})
.mock('Weapon', {canThrow: false}).using(...)
Named Bindings
Named bindings allow you to associate a specific name with a binding. This is useful when you want to differentiate between multiple bindings of the same type.
@injectable()
class Weapon { ... }
@injectable()
class Samurai {
constructor(@inject('Weapon') @named('katana') private weapon: Weapon) {}
}
You can handle these bindings by specifying the injection token and the corresponding metadata like so:
unitRef.get('Weapon', {canThrow: false})
.mock('Weapon', {canThrow: false}).using(...)
📚 Read more about named bindings in the InversifyJS documentation.
Custom Tag Decorators
InversifyJS allows for advanced dependency injection patterns using custom tag decorators. These decorators add specific metadata to the classes and their properties, guiding the IoC container on how to resolve the dependencies.
Example:
const throwable = tagged('canThrow', true);
const notThrowable = tagged('canThrow', false);
@injectable()
class Ninja {
constructor(
@inject('Weapon') @notThrowable private katana: Weapon,
@inject('Weapon') @throwable private shuriken: Weapon
) {}
}
📚 Read more about custom tag decorators in the InversifyJS documentation.
Contextual Bindings
Contextual bindings in InversifyJS provide a way to conditionally resolve bindings based on the context in which they are requested. This allows for more dynamic and flexible dependency resolution.
@injectable()
class Ninja implements Ninja {
// ...
}
Automock can handle these using the targetName
metadata:
unitRef.get('Weapon', {targetName: 'katana'})
.mock('Weapon', {targetName: 'katana'}).using(...)
📚 Read more about contextual bindings in the InversifyJS documentation.
Unmanaged Dependencies
For unmanaged dependencies, you can use the unmanaged
metadata:
class Something {
public constructor(@unmanaged private dep: SomeClass) {}
}
In Automock:
unitRef.get(SomeClass, { unmanaged: true })
.mock('SomeClass', { unmanaged: true }).using(...)
Additional Considerations
Injection Token Priority: When both class and injection tokens are present, the injection token takes precedence. For instance:
class Weapon { ... }
class Service {
public constructor(@inject("Weapon") @targetName("katana") katana: Weapon) {}
}
In this case, unitRef.get('Weapon')
is valid, while unitRef.get(Weapon)
is not.
Base Identifier Resolution: It's possible to resolve dependencies using only the base identifier (class or injection token), without metadata. This approach is valid but may require metadata for precise targeting in cases with multiple corresponding dependencies.