Skip to main content

Identifiers & Injection Tokens

Throughout the Automock documentation, you'll encounter references to identifiers, guiding you on how to use them effectively in different contexts.

In Dependency Injection (DI), identifiers play a pivotal role in determining how dependencies are provided and resolved. This guide delves into the nuances of identifiers, shedding light on their significance and varied implementations across DI frameworks.

note

It's important to note that the usage and support for different types of identifiers or metadata may vary from one adapter to another. If exists, refer to the specific adapter's documentation for detailed information.


Before diving in, ensure you've installed the appropriate adapter for your DI framework:

$ npm i -D @automock/adapters.nestjs

💡 See the full installation guide here

Class-based Injection​

Class-based injection is a fundamental concept in Dependency Injection frameworks. It revolves around the idea of using actual TypeScript classes both as a blueprint for creating instances and as an identifier for fetching specific dependencies.

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
class UserService {
constructor(private apiService: ApiService) {}
}

To resolve or mock a class-based dependency, just use the class itself.

UnitReference API
const { unitRef } = TestBed.create(UserService).compile();
const userApiService = unitRef.get(ApiService);
MockOverride API
const { unit, unitRef } = TestBed.create(UserService)
.mock(ApiService)
.using({ ... })
.compile();

Token-based Injection​

Tokens, which can be strings or symbols, serve as unique keys to fetch specific dependencies from the container. They're especially useful for distinguishing multiple instances of the same type or for non-class-based dependencies.

Just like class-based injection, the core idea of token-based injection remains consistent across different DI frameworks, but the syntax and nuances differ. Below, we've provided examples for different frameworks to showcase how token-based injection is implemented in each.

Class as Token​

Sometimes, classes double up as injection tokens. Here's how:

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
class UserService {
constructor(@Inject(ApiService) private apiService: ApiService) {}
}

Resolving or mocking such dependencies is straightforward; use the class itself:

UnitReference API
const { unitRef } = TestBed.create(UserService).compile();
const userApiService = unitRef.get(ApiService);
MockOverride API
const { unit, unitRef } = TestBed.create(UserService)
.mock(ApiService)
.using({ ... })
.compile();

String-Based / Symbol-Based Tokens​

Tokens can be strings or symbols, each serving as a unique identifier for a dependency.

Consider the following Logger interface, it will serve as an example for this part.

interface Logger {
log(message: string): void;
}

Also, consider the following constant as a token:

export const LOGGER_TOKEN = 'LOGGER_TOKEN'

You probably added this token to your dependency injection framework so that it might find a class or object that corresponds to this interface and inject its implementation.

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
class UserService {
constructor(@Inject(LOGGER_TOKEN) private logger: Logger) {}
}

To resolve or mock a token-based dependency, use the token itself, whether it's a string or symbol:

UnitReference API
const { unitRef } = TestBed.create(UserService).compile();
const logger = unitRef.get<Logger>(LOGGER_TOKEN);
MockOverride API
const { unit, unitRef } = TestBed.create(UserService)
.mock<Logger>(LOGGER_TOKEN)
.using({ ... })
.compile();

Circular Dependencies and Lazy Loading​

Circular dependencies, where two classes interdependent on each other, can be tricky. While best avoided, certain scenarios necessitate them. DI frameworks often provide mechanisms like lazy loading to handle such cases.

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
class UserService {
constructor(@Inject(forwardRef(() => ApiService)) private apiService: ApiService) {}
}

Resolving or mocking a circular dependency is akin to handling a regular identifier, sans the circular dependency wrapper.