Skip to main content

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.