Files
nest/packages/testing/testing-injector.ts
Jiri Hajek a453b6375e fix(core): fix race condition in class dependency resolution
Fix race condition in class dependency resolution, which could
otherwise lead to undefined or null injection. Split the resolution
process in 2 parts with barrier synchronization in between to ensure all
dependencies are present in dependant's instance wrapper and the
staticity of its dependency tree is evaluated correctly.

Closes #4873
2025-07-15 16:47:45 +02:00

116 lines
2.8 KiB
TypeScript

import { NestContainer } from '@nestjs/core';
import { STATIC_CONTEXT } from '@nestjs/core/injector/constants';
import {
Injector,
InjectorDependencyContext,
} from '@nestjs/core/injector/injector';
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import { Module } from '@nestjs/core/injector/module';
import { MockFactory } from './interfaces';
/**
* @publicApi
*/
export class TestingInjector extends Injector {
protected mocker?: MockFactory;
protected container: NestContainer;
public setMocker(mocker: MockFactory) {
this.mocker = mocker;
}
public setContainer(container: NestContainer) {
this.container = container;
}
public async resolveComponentWrapper<T>(
moduleRef: Module,
name: any,
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
keyOrIndex?: string | number,
): Promise<InstanceWrapper> {
try {
const existingProviderWrapper = await super.resolveComponentWrapper(
moduleRef,
name,
dependencyContext,
wrapper,
contextId,
inquirer,
keyOrIndex,
);
return existingProviderWrapper;
} catch (err) {
return this.mockWrapper(err, moduleRef, name, wrapper);
}
}
public async resolveComponentHost<T>(
moduleRef: Module,
instanceWrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<InstanceWrapper> {
try {
const existingProviderWrapper = await super.resolveComponentHost(
moduleRef,
instanceWrapper,
contextId,
inquirer,
);
return existingProviderWrapper;
} catch (err) {
return this.mockWrapper(
err,
moduleRef,
instanceWrapper.name,
instanceWrapper,
);
}
}
private async mockWrapper<T>(
err: Error,
moduleRef: Module,
name: any,
wrapper: InstanceWrapper<T>,
): Promise<InstanceWrapper> {
if (!this.mocker) {
throw err;
}
const mockedInstance = this.mocker(name);
if (!mockedInstance) {
throw err;
}
const newWrapper = new InstanceWrapper({
name,
isAlias: false,
scope: wrapper.scope,
instance: mockedInstance,
isResolved: true,
host: moduleRef,
metatype: wrapper.metatype,
});
const internalCoreModule = this.container.getInternalCoreModuleRef();
if (!internalCoreModule) {
throw new Error(
'Expected to have internal core module reference at this point.',
);
}
internalCoreModule.addCustomProvider(
{
provide: name,
useValue: mockedInstance,
},
internalCoreModule.providers,
);
internalCoreModule.addExportedProviderOrModule(name);
return newWrapper;
}
}