Skip to main content

Working with InversifyJS Identifiers 🧠

InversifyJS has unique concepts like different binding types and custom decorators that affect how you work with dependency injection. This guide covers InversifyJS-specific features and how Suites integrates with them.

What You'll Find in This Section​

  • Multi-Inject Tokens - Working with multiple implementations of a dependency
  • Tagged Bindings - Differentiating between similar dependencies
  • Named Bindings - Using names to identify specific implementations
  • Custom Decorators - Handling advanced InversifyJS patterns

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[]) {}
}

In Suites, the @multiInject decorator works the same as the @inject decorator. See Token-based injection for more details.

Learn More

Read more about multi-injection in the InversifyJS documentation.

Handling Metadata with InversifyJS 🧠​

InversifyJS supports various types of metadata to provide additional context for dependency resolution. Suites supports these metadata patterns to give you full control over your test doubles.

Tagged Bindings​

Tagged bindings differentiate between multiple bindings of the same service:

@injectable()
class Ninja {
public constructor(
@inject("Weapon") @tagged("canThrow", false) private katana: Weapon,
@inject("Weapon") @tagged("canThrow", true) private shuriken: Weapon
) {}
}

In Suites, you can access these dependencies by specifying both the injection token and the corresponding metadata:

// Get the non-throwable weapon
const katana = unitRef.get('Weapon', {canThrow: false});

// Get the throwable weapon
const shuriken = unitRef.get('Weapon', {canThrow: true});

// Mock the throwable weapon
await TestBed.solitary(Ninja)
.mock('Weapon', {canThrow: true})
.impl(stub => ({
// mock implementation
}))
.compile();

Named Bindings​

Named bindings associate a specific name with a binding:

@injectable()
class Weapon { ... }

@injectable()
class Samurai {
constructor(@inject('Weapon') @named('katana') private weapon: Weapon) {}
}

In Suites, you can access named bindings using the metadata parameter:

const katana = unitRef.get('Weapon', {name: 'katana'});

// For mocking
await TestBed.solitary(Samurai)
.mock('Weapon', {name: 'katana'})
.impl(stub => ({
// mock implementation
}))
.compile();
Learn More

Read more about named bindings in the InversifyJS documentation.

Custom Tag Decorators 💡​

InversifyJS supports custom tag decorators for advanced dependency injection patterns:

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
) {}
}

Suites handles these custom decorators through the metadata parameter in the same way as other tagged bindings.

Learn More

Read more about custom tag decorators in the InversifyJS documentation.

Contextual Bindings​

Contextual bindings allow for conditional dependency resolution based on context:

@injectable()
class Ninja implements Ninja {
// ...
}

In Suites, you can access contextual bindings using the targetName metadata:

// Get a dependency with context
const weapon = unitRef.get('Weapon', {targetName: 'katana'});

// For mocking
await TestBed.solitary(Ninja)
.mock('Weapon', {targetName: 'katana'})
.impl(stub => ({
// mock implementation
}))
.compile();
Learn More

Read more about contextual bindings in the InversifyJS documentation.

Unmanaged Dependencies​

For dependencies that InversifyJS doesn't manage, you can use the unmanaged metadata:

class Something {
public constructor(@unmanaged private dep: SomeClass) {}
}

In Suites:

// Get unmanaged dependency
const dep = unitRef.get(SomeClass, { unmanaged: true });

// For mocking
await TestBed.solitary(Something)
.mock(SomeClass, { unmanaged: true })
.impl(stub => ({
// mock implementation
}))
.compile();

Additional Considerations 💡​

Injection Token Priority​

When both class and injection tokens are present, the injection token takes precedence:

class Weapon { ... }

class Service {
public constructor(@inject("Weapon") @targetName("katana") katana: Weapon) {}
}

In this case, unitRef.get('Weapon') is valid, while unitRef.get(Weapon) will not work because the explicit injection token is used.

Base Identifier Resolution​

You can resolve dependencies using only the base identifier (class or injection token) without metadata. This approach works but may not be precise enough when there are multiple matching dependencies.

Next Steps​