mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
chore: resolve conflicts
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import { Injectable, Module, forwardRef } from '@nestjs/common';
|
||||
import { BModule, BProvider } from './b.module';
|
||||
|
||||
@Injectable()
|
||||
export class AProvider {}
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => BModule)],
|
||||
providers: [AProvider],
|
||||
exports: [AProvider],
|
||||
})
|
||||
export class AModule {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Injectable, Module, forwardRef } from '@nestjs/common';
|
||||
import { AModule, AProvider } from './a.module';
|
||||
|
||||
@Injectable()
|
||||
export class BProvider {}
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => AModule)],
|
||||
providers: [BProvider],
|
||||
exports: [BProvider],
|
||||
})
|
||||
export class BModule {}
|
||||
309
integration/testing-module-override/e2e/modules-override.spec.ts
Normal file
309
integration/testing-module-override/e2e/modules-override.spec.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
import {
|
||||
Controller,
|
||||
DynamicModule,
|
||||
forwardRef,
|
||||
Global,
|
||||
Injectable,
|
||||
Module,
|
||||
} from '@nestjs/common';
|
||||
import { LazyModuleLoader } from '@nestjs/core';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { AModule, AProvider } from './circular-dependency/a.module';
|
||||
import { BModule, BProvider } from './circular-dependency/b.module';
|
||||
|
||||
describe('Modules overriding', () => {
|
||||
describe('Top-level module', () => {
|
||||
@Controller()
|
||||
class ControllerOverwritten {}
|
||||
|
||||
@Module({
|
||||
controllers: [ControllerOverwritten],
|
||||
})
|
||||
class ModuleToBeOverwritten {}
|
||||
|
||||
@Controller()
|
||||
class ControllerOverride {}
|
||||
|
||||
@Module({
|
||||
controllers: [ControllerOverride],
|
||||
})
|
||||
class ModuleOverride {}
|
||||
|
||||
let testingModule: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [ModuleToBeOverwritten],
|
||||
})
|
||||
.overrideModule(ModuleToBeOverwritten)
|
||||
.useModule(ModuleOverride)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override top-level modules using testing module builder', () => {
|
||||
expect(() =>
|
||||
testingModule.get<ControllerOverwritten>(ControllerOverwritten),
|
||||
).to.throw();
|
||||
expect(
|
||||
testingModule.get<ControllerOverride>(ControllerOverride),
|
||||
).to.be.an.instanceof(ControllerOverride);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dynamic module', () => {
|
||||
@Controller()
|
||||
class ControllerOverwritten {}
|
||||
|
||||
@Module({})
|
||||
class DynamicModuleToBeOverwritten {}
|
||||
|
||||
const dynamicModuleOverwritten: DynamicModule = {
|
||||
module: DynamicModuleToBeOverwritten,
|
||||
controllers: [ControllerOverwritten],
|
||||
};
|
||||
|
||||
@Controller()
|
||||
class ControllerOverride {}
|
||||
|
||||
@Module({})
|
||||
class DynamicModuleOverride {}
|
||||
|
||||
const dynamicModuleOverride: DynamicModule = {
|
||||
module: DynamicModuleOverride,
|
||||
controllers: [ControllerOverride],
|
||||
};
|
||||
|
||||
let testingModule: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [dynamicModuleOverwritten],
|
||||
})
|
||||
.overrideModule(dynamicModuleOverwritten)
|
||||
.useModule(dynamicModuleOverride)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override dynamic modules using testing module builder', () => {
|
||||
expect(() =>
|
||||
testingModule.get<ControllerOverwritten>(ControllerOverwritten),
|
||||
).to.throw();
|
||||
expect(
|
||||
testingModule.get<ControllerOverride>(ControllerOverride),
|
||||
).to.be.an.instanceof(ControllerOverride);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Circular dependency module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Injectable()
|
||||
class CProvider {}
|
||||
|
||||
@Module({
|
||||
providers: [CProvider],
|
||||
})
|
||||
class CModule {}
|
||||
|
||||
@Injectable()
|
||||
class BProviderOverride {}
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => AModule), forwardRef(() => CModule)],
|
||||
providers: [BProviderOverride],
|
||||
exports: [BProviderOverride],
|
||||
})
|
||||
class BModuleOverride {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [AModule],
|
||||
})
|
||||
.overrideModule(BModule)
|
||||
.useModule(BModuleOverride)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override top-level modules using testing module builder', () => {
|
||||
expect(testingModule.get<AProvider>(AProvider)).to.be.an.instanceof(
|
||||
AProvider,
|
||||
);
|
||||
expect(() => testingModule.get<BProvider>(BProvider)).to.throw();
|
||||
expect(testingModule.get<CProvider>(CProvider)).to.be.an.instanceof(
|
||||
CProvider,
|
||||
);
|
||||
expect(
|
||||
testingModule.get<BProviderOverride>(BProviderOverride),
|
||||
).to.be.an.instanceof(BProviderOverride);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Nested module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Controller()
|
||||
class OverwrittenNestedModuleController {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverwrittenNestedModuleController],
|
||||
})
|
||||
class OverwrittenNestedModule {}
|
||||
|
||||
@Controller()
|
||||
class OverrideNestedModuleController {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverrideNestedModuleController],
|
||||
})
|
||||
class OverrideNestedModule {}
|
||||
|
||||
@Module({
|
||||
imports: [OverwrittenNestedModule],
|
||||
})
|
||||
class AppModule {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
})
|
||||
.overrideModule(OverwrittenNestedModule)
|
||||
.useModule(OverrideNestedModule)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override nested modules using testing module builder', () => {
|
||||
expect(
|
||||
testingModule.get<OverrideNestedModuleController>(
|
||||
OverrideNestedModuleController,
|
||||
),
|
||||
).to.be.an.instanceof(OverrideNestedModuleController);
|
||||
expect(() =>
|
||||
testingModule.get<OverwrittenNestedModuleController>(
|
||||
OverwrittenNestedModuleController,
|
||||
),
|
||||
).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Lazy-loaded module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Injectable()
|
||||
class OverwrittenLazyProvider {
|
||||
value() {
|
||||
return 'overwritten lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: 'LAZY_PROVIDER',
|
||||
useClass: OverwrittenLazyProvider,
|
||||
},
|
||||
],
|
||||
})
|
||||
class OverwrittenLazyModule {}
|
||||
|
||||
@Injectable()
|
||||
class OverrideLazyProvider {
|
||||
value() {
|
||||
return 'override lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: 'LAZY_PROVIDER',
|
||||
useClass: OverrideLazyProvider,
|
||||
},
|
||||
],
|
||||
})
|
||||
class OverrideLazyModule {}
|
||||
|
||||
@Injectable()
|
||||
class AppService {
|
||||
constructor(private lazyModuleLoader: LazyModuleLoader) {}
|
||||
|
||||
async value() {
|
||||
const moduleRef = await this.lazyModuleLoader.load(
|
||||
() => OverwrittenLazyModule,
|
||||
);
|
||||
return moduleRef.get('LAZY_PROVIDER').value();
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [AppService],
|
||||
})
|
||||
class AppModule {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
})
|
||||
.overrideModule(OverwrittenLazyModule)
|
||||
.useModule(OverrideLazyModule)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override lazy loaded modules using testing module builder', async () => {
|
||||
const result = await testingModule.get<AppService>(AppService).value();
|
||||
expect(result).to.be.equal('override lazy');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global module', () => {
|
||||
let testingModule: TestingModule;
|
||||
|
||||
@Injectable()
|
||||
class OverwrittenProvider {
|
||||
value() {
|
||||
return 'overwritten lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [OverwrittenProvider],
|
||||
exports: [OverwrittenProvider],
|
||||
})
|
||||
class OverwrittenModule {}
|
||||
|
||||
@Injectable()
|
||||
class OverrideProvider {
|
||||
value() {
|
||||
return 'override lazy';
|
||||
}
|
||||
}
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [OverrideProvider],
|
||||
exports: [OverrideProvider],
|
||||
})
|
||||
class OverrideModule {}
|
||||
|
||||
beforeEach(async () => {
|
||||
testingModule = await Test.createTestingModule({
|
||||
imports: [OverwrittenModule],
|
||||
})
|
||||
.overrideModule(OverwrittenModule)
|
||||
.useModule(OverrideModule)
|
||||
.compile();
|
||||
});
|
||||
|
||||
it('should override global modules using testing module builder', () => {
|
||||
expect(
|
||||
testingModule.get<OverrideProvider>(OverrideProvider),
|
||||
).to.be.an.instanceof(OverrideProvider);
|
||||
expect(() =>
|
||||
testingModule.get<OverwrittenProvider>(OverwrittenProvider),
|
||||
).to.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
17
integration/testing-module-override/tsconfig.json
Normal file
17
integration/testing-module-override/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2017",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { InitializeOnPreviewAllowlist } from '../inspector/initialize-on-preview.allowlist';
|
||||
import { SerializedGraph } from '../inspector/serialized-graph';
|
||||
import { REQUEST } from '../router/request/request-constants';
|
||||
import { ModuleCompiler } from './compiler';
|
||||
import { ModuleCompiler, ModuleFactory } from './compiler';
|
||||
import { ContextId } from './instance-wrapper';
|
||||
import { InternalCoreModule } from './internal-core-module/internal-core-module';
|
||||
import { InternalProvidersStorage } from './internal-providers-storage';
|
||||
@@ -21,6 +21,9 @@ import { Module } from './module';
|
||||
import { ModuleTokenFactory } from './module-token-factory';
|
||||
import { ModulesContainer } from './modules-container';
|
||||
|
||||
type ModuleMetatype = Type<any> | DynamicModule | Promise<DynamicModule>;
|
||||
type ModuleScope = Type<any>[];
|
||||
|
||||
export class NestContainer {
|
||||
private readonly globalModules = new Set<Module>();
|
||||
private readonly moduleTokenFactory = new ModuleTokenFactory();
|
||||
@@ -65,8 +68,8 @@ export class NestContainer {
|
||||
}
|
||||
|
||||
public async addModule(
|
||||
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
|
||||
scope: Type<any>[],
|
||||
metatype: ModuleMetatype,
|
||||
scope: ModuleScope,
|
||||
): Promise<Module | undefined> {
|
||||
// In DependenciesScanner#scanForModules we already check for undefined or invalid modules
|
||||
// We still need to catch the edge-case of `forwardRef(() => undefined)`
|
||||
@@ -79,6 +82,47 @@ export class NestContainer {
|
||||
if (this.modules.has(token)) {
|
||||
return this.modules.get(token);
|
||||
}
|
||||
|
||||
return this.setModule(
|
||||
{
|
||||
token,
|
||||
type,
|
||||
dynamicMetadata,
|
||||
},
|
||||
scope,
|
||||
);
|
||||
}
|
||||
|
||||
public async replaceModule(
|
||||
metatypeToReplace: ModuleMetatype,
|
||||
newMetatype: ModuleMetatype,
|
||||
scope: ModuleScope,
|
||||
): Promise<Module | undefined> {
|
||||
// In DependenciesScanner#scanForModules we already check for undefined or invalid modules
|
||||
// We still need to catch the edge-case of `forwardRef(() => undefined)`
|
||||
if (!metatypeToReplace || !newMetatype) {
|
||||
throw new UndefinedForwardRefException(scope);
|
||||
}
|
||||
|
||||
const { token } = await this.moduleCompiler.compile(metatypeToReplace);
|
||||
const { type, dynamicMetadata } = await this.moduleCompiler.compile(
|
||||
newMetatype,
|
||||
);
|
||||
|
||||
return this.setModule(
|
||||
{
|
||||
token,
|
||||
type,
|
||||
dynamicMetadata,
|
||||
},
|
||||
scope,
|
||||
);
|
||||
}
|
||||
|
||||
private async setModule(
|
||||
{ token, dynamicMetadata, type }: ModuleFactory,
|
||||
scope: ModuleScope,
|
||||
): Promise<Module | undefined> {
|
||||
const moduleRef = new Module(type, this);
|
||||
moduleRef.token = token;
|
||||
moduleRef.initOnPreview = this.shouldInitOnPreview(type);
|
||||
@@ -91,6 +135,7 @@ export class NestContainer {
|
||||
moduleRef.isGlobal = true;
|
||||
this.addGlobalModule(moduleRef);
|
||||
}
|
||||
|
||||
return moduleRef;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ExternalContextCreator } from '../../helpers/external-context-creator';
|
||||
import { HttpAdapterHost } from '../../helpers/http-adapter-host';
|
||||
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||
import { SerializedGraph } from '../../inspector/serialized-graph';
|
||||
import { ModuleOverride } from '../../interfaces/module-override.interface';
|
||||
import { DependenciesScanner } from '../../scanner';
|
||||
import { ModuleCompiler } from '../compiler';
|
||||
import { NestContainer } from '../container';
|
||||
@@ -19,6 +20,7 @@ export class InternalCoreModuleFactory {
|
||||
moduleCompiler: ModuleCompiler,
|
||||
httpAdapterHost: HttpAdapterHost,
|
||||
graphInspector: GraphInspector,
|
||||
moduleOverrides?: ModuleOverride[],
|
||||
) {
|
||||
const lazyModuleLoaderFactory = () => {
|
||||
const logger = new Logger(LazyModuleLoader.name, {
|
||||
@@ -36,6 +38,7 @@ export class InternalCoreModuleFactory {
|
||||
instanceLoader,
|
||||
moduleCompiler,
|
||||
container.getModules(),
|
||||
moduleOverrides,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DynamicModule, Type } from '@nestjs/common';
|
||||
import { ModuleOverride } from '../../interfaces/module-override.interface';
|
||||
import { DependenciesScanner } from '../../scanner';
|
||||
import { ModuleCompiler } from '../compiler';
|
||||
import { SilentLogger } from '../helpers/silent-logger';
|
||||
@@ -14,6 +15,7 @@ export class LazyModuleLoader {
|
||||
private readonly instanceLoader: InstanceLoader,
|
||||
private readonly moduleCompiler: ModuleCompiler,
|
||||
private readonly modulesContainer: ModulesContainer,
|
||||
private readonly moduleOverrides?: ModuleOverride[],
|
||||
) {}
|
||||
|
||||
public async load(
|
||||
@@ -26,9 +28,10 @@ export class LazyModuleLoader {
|
||||
this.registerLoggerConfiguration(loadOpts);
|
||||
|
||||
const moduleClassOrDynamicDefinition = await loaderFn();
|
||||
const moduleInstances = await this.dependenciesScanner.scanForModules(
|
||||
moduleClassOrDynamicDefinition,
|
||||
);
|
||||
const moduleInstances = await this.dependenciesScanner.scanForModules({
|
||||
moduleDefinition: moduleClassOrDynamicDefinition,
|
||||
overrides: this.moduleOverrides,
|
||||
});
|
||||
if (moduleInstances.length === 0) {
|
||||
// The module has been loaded already. In this case, we must
|
||||
// retrieve a module reference from the existing container.
|
||||
|
||||
8
packages/core/interfaces/module-definition.interface.ts
Normal file
8
packages/core/interfaces/module-definition.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { DynamicModule, ForwardReference } from '@nestjs/common';
|
||||
import { Type } from '@nestjs/common/interfaces';
|
||||
|
||||
export type ModuleDefinition =
|
||||
| ForwardReference
|
||||
| Type<unknown>
|
||||
| DynamicModule
|
||||
| Promise<DynamicModule>;
|
||||
6
packages/core/interfaces/module-override.interface.ts
Normal file
6
packages/core/interfaces/module-override.interface.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ModuleDefinition } from './module-definition.interface';
|
||||
|
||||
export interface ModuleOverride {
|
||||
moduleToReplace: ModuleDefinition;
|
||||
newModule: ModuleDefinition;
|
||||
}
|
||||
@@ -52,6 +52,8 @@ import { InternalCoreModuleFactory } from './injector/internal-core-module/inter
|
||||
import { Module } from './injector/module';
|
||||
import { GraphInspector } from './inspector/graph-inspector';
|
||||
import { UuidFactory } from './inspector/uuid-factory';
|
||||
import { ModuleDefinition } from './interfaces/module-definition.interface';
|
||||
import { ModuleOverride } from './interfaces/module-override.interface';
|
||||
import { MetadataScanner } from './metadata-scanner';
|
||||
|
||||
interface ApplicationProviderWrapper {
|
||||
@@ -61,6 +63,13 @@ interface ApplicationProviderWrapper {
|
||||
scope?: Scope;
|
||||
}
|
||||
|
||||
interface ModulesScanParameters {
|
||||
moduleDefinition: ModuleDefinition;
|
||||
scope?: Type<unknown>[];
|
||||
ctxRegistry?: (ForwardReference | DynamicModule | Type<unknown>)[];
|
||||
overrides?: ModuleOverride[];
|
||||
}
|
||||
|
||||
export class DependenciesScanner {
|
||||
private readonly applicationProvidersApplyMap: ApplicationProviderWrapper[] =
|
||||
[];
|
||||
@@ -72,9 +81,15 @@ export class DependenciesScanner {
|
||||
private readonly applicationConfig = new ApplicationConfig(),
|
||||
) {}
|
||||
|
||||
public async scan(module: Type<any>) {
|
||||
await this.registerCoreModule();
|
||||
await this.scanForModules(module);
|
||||
public async scan(
|
||||
module: Type<any>,
|
||||
options?: { overrides?: ModuleOverride[] },
|
||||
) {
|
||||
await this.registerCoreModule(options?.overrides);
|
||||
await this.scanForModules({
|
||||
moduleDefinition: module,
|
||||
overrides: options?.overrides,
|
||||
});
|
||||
await this.scanModulesForDependencies();
|
||||
this.calculateModulesDistance();
|
||||
|
||||
@@ -82,16 +97,21 @@ export class DependenciesScanner {
|
||||
this.container.bindGlobalScope();
|
||||
}
|
||||
|
||||
public async scanForModules(
|
||||
moduleDefinition:
|
||||
| ForwardReference
|
||||
| Type<unknown>
|
||||
| DynamicModule
|
||||
| Promise<DynamicModule>,
|
||||
scope: Type<unknown>[] = [],
|
||||
ctxRegistry: (ForwardReference | DynamicModule | Type<unknown>)[] = [],
|
||||
): Promise<Module[]> {
|
||||
const moduleInstance = await this.insertModule(moduleDefinition, scope);
|
||||
public async scanForModules({
|
||||
moduleDefinition,
|
||||
scope = [],
|
||||
ctxRegistry = [],
|
||||
overrides = [],
|
||||
}: ModulesScanParameters): Promise<Module[]> {
|
||||
const moduleInstance = await this.insertOrOverrideModule(
|
||||
moduleDefinition,
|
||||
overrides,
|
||||
scope,
|
||||
);
|
||||
|
||||
moduleDefinition =
|
||||
this.getOverrideModuleByModule(moduleDefinition, overrides)?.newModule ??
|
||||
moduleDefinition;
|
||||
moduleDefinition =
|
||||
moduleDefinition instanceof Promise
|
||||
? await moduleDefinition
|
||||
@@ -128,11 +148,12 @@ export class DependenciesScanner {
|
||||
if (ctxRegistry.includes(innerModule)) {
|
||||
continue;
|
||||
}
|
||||
const moduleRefs = await this.scanForModules(
|
||||
innerModule,
|
||||
[].concat(scope, moduleDefinition),
|
||||
const moduleRefs = await this.scanForModules({
|
||||
moduleDefinition: innerModule,
|
||||
scope: [].concat(scope, moduleDefinition),
|
||||
ctxRegistry,
|
||||
);
|
||||
overrides,
|
||||
});
|
||||
registeredModuleRefs = registeredModuleRefs.concat(moduleRefs);
|
||||
}
|
||||
if (!moduleInstance) {
|
||||
@@ -496,6 +517,60 @@ export class DependenciesScanner {
|
||||
this.container.addController(controller, token);
|
||||
}
|
||||
|
||||
private insertOrOverrideModule(
|
||||
moduleDefinition: ModuleDefinition,
|
||||
overrides: ModuleOverride[],
|
||||
scope: Type<unknown>[],
|
||||
): Promise<Module | undefined> {
|
||||
const overrideModule = this.getOverrideModuleByModule(
|
||||
moduleDefinition,
|
||||
overrides,
|
||||
);
|
||||
if (overrideModule !== undefined) {
|
||||
return this.overrideModule(
|
||||
moduleDefinition,
|
||||
overrideModule.newModule,
|
||||
scope,
|
||||
);
|
||||
}
|
||||
|
||||
return this.insertModule(moduleDefinition, scope);
|
||||
}
|
||||
|
||||
private getOverrideModuleByModule(
|
||||
module: ModuleDefinition,
|
||||
overrides: ModuleOverride[],
|
||||
): ModuleOverride | undefined {
|
||||
if (this.isForwardReference(module)) {
|
||||
return overrides.find(moduleToOverride => {
|
||||
return (
|
||||
moduleToOverride.moduleToReplace === module.forwardRef() ||
|
||||
(
|
||||
moduleToOverride.moduleToReplace as ForwardReference
|
||||
).forwardRef?.() === module.forwardRef()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return overrides.find(
|
||||
moduleToOverride => moduleToOverride.moduleToReplace === module,
|
||||
);
|
||||
}
|
||||
|
||||
private async overrideModule(
|
||||
moduleToOverride: ModuleDefinition,
|
||||
newModule: ModuleDefinition,
|
||||
scope: Type<unknown>[],
|
||||
): Promise<Module | undefined> {
|
||||
return this.container.replaceModule(
|
||||
this.isForwardReference(moduleToOverride)
|
||||
? moduleToOverride.forwardRef()
|
||||
: moduleToOverride,
|
||||
this.isForwardReference(newModule) ? newModule.forwardRef() : newModule,
|
||||
scope,
|
||||
);
|
||||
}
|
||||
|
||||
public reflectMetadata<T = any>(
|
||||
metadataKey: string,
|
||||
metatype: Type<any>,
|
||||
@@ -503,15 +578,19 @@ export class DependenciesScanner {
|
||||
return Reflect.getMetadata(metadataKey, metatype) || [];
|
||||
}
|
||||
|
||||
public async registerCoreModule() {
|
||||
public async registerCoreModule(overrides?: ModuleOverride[]) {
|
||||
const moduleDefinition = InternalCoreModuleFactory.create(
|
||||
this.container,
|
||||
this,
|
||||
this.container.getModuleCompiler(),
|
||||
this.container.getHttpAdapterHostRef(),
|
||||
this.graphInspector,
|
||||
overrides,
|
||||
);
|
||||
const [instance] = await this.scanForModules(moduleDefinition);
|
||||
const [instance] = await this.scanForModules({
|
||||
moduleDefinition,
|
||||
overrides,
|
||||
});
|
||||
this.container.registerCoreModuleRef(instance);
|
||||
}
|
||||
|
||||
@@ -637,7 +716,7 @@ export class DependenciesScanner {
|
||||
}
|
||||
|
||||
private isForwardReference(
|
||||
module: Type<any> | DynamicModule | ForwardReference,
|
||||
module: ModuleDefinition,
|
||||
): module is ForwardReference {
|
||||
return module && !!(module as ForwardReference).forwardRef;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ describe('ExceptionsHandler', () => {
|
||||
handler.setCustomFilters(filters as any);
|
||||
expect((handler as any).filters).to.be.eql(filters);
|
||||
});
|
||||
it('should throws exception when passed argument is not an array', () => {
|
||||
it('should throw exception when passed argument is not an array', () => {
|
||||
expect(() => handler.setCustomFilters(null)).to.throws(
|
||||
InvalidExceptionFilterException,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { expect } from 'chai';
|
||||
import { of } from 'rxjs';
|
||||
import * as sinon from 'sinon';
|
||||
import { ExternalExceptionsHandler } from '../../exceptions/external-exceptions-handler';
|
||||
import { ExternalExceptionFilter } from '../../exceptions/external-exception-filter';
|
||||
import { ExternalExceptionsHandler } from '../../exceptions/external-exceptions-handler';
|
||||
|
||||
describe('ExternalExceptionsHandler', () => {
|
||||
let handler: ExternalExceptionsHandler;
|
||||
@@ -37,7 +37,7 @@ describe('ExternalExceptionsHandler', () => {
|
||||
handler.setCustomFilters(filters as any);
|
||||
expect((handler as any).filters).to.be.eql(filters);
|
||||
});
|
||||
it('should throws exception when passed argument is not an array', () => {
|
||||
it('should throw exception when passed argument is not an array', () => {
|
||||
expect(() => handler.setCustomFilters(null)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('NestContainer', () => {
|
||||
expect(setSpy.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should throws an exception when metatype is not defined', () => {
|
||||
it('should throw an exception when metatype is not defined', () => {
|
||||
expect(container.addModule(undefined, [])).to.eventually.throws();
|
||||
});
|
||||
|
||||
@@ -81,6 +81,37 @@ describe('NestContainer', () => {
|
||||
expect(addGlobalModuleSpy.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceModule', () => {
|
||||
it('should replace module if already exists in collection', async () => {
|
||||
@Module({})
|
||||
class ReplaceTestModule {}
|
||||
|
||||
const modules = new Map();
|
||||
const setSpy = sinon.spy(modules, 'set');
|
||||
(container as any).modules = modules;
|
||||
|
||||
await container.addModule(TestModule as any, []);
|
||||
await container.replaceModule(
|
||||
TestModule as any,
|
||||
ReplaceTestModule as any,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(setSpy.calledTwice).to.be.true;
|
||||
});
|
||||
|
||||
it('should throw an exception when metatype is not defined', () => {
|
||||
expect(container.addModule(undefined, [])).to.eventually.throws();
|
||||
});
|
||||
|
||||
it('should add global module when module is global', async () => {
|
||||
const addGlobalModuleSpy = sinon.spy(container, 'addGlobalModule');
|
||||
await container.addModule(GlobalTestModule as any, []);
|
||||
expect(addGlobalModuleSpy.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('isGlobalModule', () => {
|
||||
describe('when module is not globally scoped', () => {
|
||||
it('should return false', () => {
|
||||
|
||||
@@ -306,7 +306,7 @@ describe('Module', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub((module as any)._providers, 'has').returns(false);
|
||||
});
|
||||
it('should throws RuntimeException', () => {
|
||||
it('should throw RuntimeException', () => {
|
||||
expect(() => module.instance).to.throws(RuntimeException);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { UndefinedModuleException } from '../errors/exceptions/undefined-module.
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { ModuleOverride } from '../interfaces/module-override.interface';
|
||||
import { MetadataScanner } from '../metadata-scanner';
|
||||
import { DependenciesScanner } from '../scanner';
|
||||
import Sinon = require('sinon');
|
||||
@@ -77,11 +78,17 @@ describe('DependenciesScanner', () => {
|
||||
mockContainer.restore();
|
||||
});
|
||||
|
||||
it('should "insertModule" call twice (2 modules) container method "addModule"', async () => {
|
||||
const expectation = mockContainer.expects('addModule').twice();
|
||||
it('should "insertOrOverrideModule" call twice (2 modules) container method "addModule"', async () => {
|
||||
const expectationCountAddModule = mockContainer
|
||||
.expects('addModule')
|
||||
.twice();
|
||||
const expectationCountReplaceModule = mockContainer
|
||||
.expects('replaceModule')
|
||||
.never();
|
||||
|
||||
await scanner.scan(TestModule);
|
||||
expectation.verify();
|
||||
await scanner.scan(TestModule as any);
|
||||
expectationCountAddModule.verify();
|
||||
expectationCountReplaceModule.verify();
|
||||
});
|
||||
|
||||
it('should "insertProvider" call twice (2 components) container method "addProvider"', async () => {
|
||||
@@ -105,6 +112,138 @@ describe('DependenciesScanner', () => {
|
||||
expectation.verify();
|
||||
});
|
||||
|
||||
describe('when there is modules overrides', () => {
|
||||
@Injectable()
|
||||
class OverwrittenTestComponent {}
|
||||
|
||||
@Controller('')
|
||||
class OverwrittenControlerOne {}
|
||||
|
||||
@Controller('')
|
||||
class OverwrittenControllerTwo {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverwrittenControlerOne],
|
||||
providers: [OverwrittenTestComponent],
|
||||
})
|
||||
class OverwrittenModuleOne {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverwrittenControllerTwo],
|
||||
})
|
||||
class OverwrittenModuleTwo {}
|
||||
|
||||
@Module({
|
||||
imports: [OverwrittenModuleOne, OverwrittenModuleTwo],
|
||||
})
|
||||
class OverrideTestModule {}
|
||||
|
||||
@Injectable()
|
||||
class OverrideTestComponent {}
|
||||
|
||||
@Controller('')
|
||||
class OverrideControllerOne {}
|
||||
|
||||
@Controller('')
|
||||
class OverrideControllerTwo {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverwrittenControlerOne],
|
||||
providers: [OverrideTestComponent],
|
||||
})
|
||||
class OverrideModuleOne {}
|
||||
|
||||
@Module({
|
||||
controllers: [OverrideControllerTwo],
|
||||
})
|
||||
class OverrideModuleTwo {}
|
||||
|
||||
const modulesToOverride: ModuleOverride[] = [
|
||||
{ moduleToReplace: OverwrittenModuleOne, newModule: OverrideModuleOne },
|
||||
{ moduleToReplace: OverwrittenModuleTwo, newModule: OverrideModuleTwo },
|
||||
];
|
||||
|
||||
it('should "putModule" call twice (2 modules) container method "replaceModule"', async () => {
|
||||
const expectationReplaceModuleFirst = mockContainer
|
||||
.expects('replaceModule')
|
||||
.once()
|
||||
.withArgs(OverwrittenModuleOne, OverrideModuleOne, sinon.match.array);
|
||||
const expectationReplaceModuleSecond = mockContainer
|
||||
.expects('replaceModule')
|
||||
.once()
|
||||
.withArgs(OverwrittenModuleTwo, OverrideModuleTwo, sinon.match.array);
|
||||
const expectationCountAddModule = mockContainer
|
||||
.expects('addModule')
|
||||
.once();
|
||||
|
||||
await scanner.scan(OverrideTestModule as any, {
|
||||
overrides: modulesToOverride,
|
||||
});
|
||||
|
||||
expectationReplaceModuleFirst.verify();
|
||||
expectationReplaceModuleSecond.verify();
|
||||
expectationCountAddModule.verify();
|
||||
});
|
||||
|
||||
it('should "insertProvider" call once container method "addProvider"', async () => {
|
||||
const expectation = mockContainer.expects('addProvider').once();
|
||||
|
||||
await scanner.scan(OverrideTestModule as any);
|
||||
expectation.verify();
|
||||
});
|
||||
|
||||
it('should "insertController" call twice (2 components) container method "addController"', async () => {
|
||||
const expectation = mockContainer.expects('addController').twice();
|
||||
await scanner.scan(OverrideTestModule as any);
|
||||
expectation.verify();
|
||||
});
|
||||
|
||||
it('should "putModule" call container method "replaceModule" with forwardRef() when forwardRef property exists', async () => {
|
||||
const overwrittenForwardRefSpy = sinon.spy();
|
||||
|
||||
@Module({})
|
||||
class OverwrittenForwardRef {}
|
||||
|
||||
@Module({})
|
||||
class Overwritten {
|
||||
public static forwardRef() {
|
||||
overwrittenForwardRefSpy();
|
||||
return OverwrittenForwardRef;
|
||||
}
|
||||
}
|
||||
|
||||
const overrideForwardRefSpy = sinon.spy();
|
||||
|
||||
@Module({})
|
||||
class OverrideForwardRef {}
|
||||
|
||||
@Module({})
|
||||
class Override {
|
||||
public static forwardRef() {
|
||||
overrideForwardRefSpy();
|
||||
return OverrideForwardRef;
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [Overwritten],
|
||||
})
|
||||
class OverrideForwardRefTestModule {}
|
||||
|
||||
await scanner.scan(OverrideForwardRefTestModule as any, {
|
||||
overrides: [
|
||||
{
|
||||
moduleToReplace: Overwritten,
|
||||
newModule: Override,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(overwrittenForwardRefSpy.called).to.be.true;
|
||||
expect(overrideForwardRefSpy.called).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('reflectDynamicMetadata', () => {
|
||||
describe('when param has prototype', () => {
|
||||
it('should call "reflectParamInjectables" and "reflectInjectables"', () => {
|
||||
@@ -595,14 +734,20 @@ describe('DependenciesScanner', () => {
|
||||
describe('scanForModules', () => {
|
||||
it('should throw an exception when the imports array includes undefined', () => {
|
||||
try {
|
||||
scanner.scanForModules(UndefinedModule, [UndefinedModule]);
|
||||
scanner.scanForModules({
|
||||
moduleDefinition: UndefinedModule,
|
||||
scope: [UndefinedModule],
|
||||
});
|
||||
} catch (exception) {
|
||||
expect(exception instanceof UndefinedModuleException).to.be.true;
|
||||
}
|
||||
});
|
||||
it('should throw an exception when the imports array includes an invalid value', () => {
|
||||
try {
|
||||
scanner.scanForModules(InvalidModule, [InvalidModule]);
|
||||
scanner.scanForModules({
|
||||
moduleDefinition: InvalidModule,
|
||||
scope: [InvalidModule],
|
||||
});
|
||||
} catch (exception) {
|
||||
expect(exception instanceof InvalidModuleException).to.be.true;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('RpcContextCreator', () => {
|
||||
expect(tryActivateSpy.called).to.be.true;
|
||||
});
|
||||
describe('when can not activate', () => {
|
||||
it('should throws forbidden exception', async () => {
|
||||
it('should throw forbidden exception', async () => {
|
||||
sinon
|
||||
.stub(guardsConsumer, 'tryActivate')
|
||||
.callsFake(async () => false);
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('RpcExceptionsHandler', () => {
|
||||
handler.setCustomFilters(filters as any);
|
||||
expect((handler as any).filters).to.be.eql(filters);
|
||||
});
|
||||
it('should throws exception when passed argument is not an array', () => {
|
||||
it('should throw exception when passed argument is not an array', () => {
|
||||
expect(() => handler.setCustomFilters(null)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
9
packages/testing/interfaces/override-module.interface.ts
Normal file
9
packages/testing/interfaces/override-module.interface.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ModuleDefinition } from '../../core/interfaces/module-definition.interface';
|
||||
import { TestingModuleBuilder } from '../testing-module.builder';
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export interface OverrideModule {
|
||||
useModule: (newModule: ModuleDefinition) => TestingModuleBuilder;
|
||||
}
|
||||
@@ -10,11 +10,14 @@ import {
|
||||
} from '@nestjs/core/inspector/uuid-factory';
|
||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||
import { DependenciesScanner } from '@nestjs/core/scanner';
|
||||
import { ModuleDefinition } from '../core/interfaces/module-definition.interface';
|
||||
import { ModuleOverride } from '../core/interfaces/module-override.interface';
|
||||
import {
|
||||
MockFactory,
|
||||
OverrideBy,
|
||||
OverrideByFactoryOptions,
|
||||
} from './interfaces';
|
||||
import { OverrideModule } from './interfaces/override-module.interface';
|
||||
import { TestingLogger } from './services/testing-logger.service';
|
||||
import { TestingInjector } from './testing-injector';
|
||||
import { TestingInstanceLoader } from './testing-instance-loader';
|
||||
@@ -27,6 +30,10 @@ export class TestingModuleBuilder {
|
||||
private readonly applicationConfig = new ApplicationConfig();
|
||||
private readonly container = new NestContainer(this.applicationConfig);
|
||||
private readonly overloadsMap = new Map();
|
||||
private readonly moduleOverloadsMap = new Map<
|
||||
ModuleDefinition,
|
||||
ModuleDefinition
|
||||
>();
|
||||
private readonly module: any;
|
||||
private testingLogger: LoggerService;
|
||||
private mocker?: MockFactory;
|
||||
@@ -68,6 +75,15 @@ export class TestingModuleBuilder {
|
||||
return this.override(typeOrToken, true);
|
||||
}
|
||||
|
||||
public overrideModule(moduleToOverride: ModuleDefinition): OverrideModule {
|
||||
return {
|
||||
useModule: newModule => {
|
||||
this.moduleOverloadsMap.set(moduleToOverride, newModule);
|
||||
return this;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async compile(
|
||||
options: Pick<NestApplicationContextOptions, 'snapshot' | 'preview'> = {},
|
||||
): Promise<TestingModule> {
|
||||
@@ -88,7 +104,9 @@ export class TestingModuleBuilder {
|
||||
graphInspector,
|
||||
this.applicationConfig,
|
||||
);
|
||||
await scanner.scan(this.module);
|
||||
await scanner.scan(this.module, {
|
||||
overrides: this.getModuleOverloads(),
|
||||
});
|
||||
|
||||
this.applyOverloadsMap();
|
||||
await this.createInstancesOfDependencies(graphInspector, options);
|
||||
@@ -126,11 +144,20 @@ export class TestingModuleBuilder {
|
||||
}
|
||||
|
||||
private applyOverloadsMap() {
|
||||
[...this.overloadsMap.entries()].forEach(([item, options]) => {
|
||||
const overloads = [...this.overloadsMap.entries()];
|
||||
overloads.forEach(([item, options]) => {
|
||||
this.container.replace(item, options);
|
||||
});
|
||||
}
|
||||
|
||||
private getModuleOverloads(): ModuleOverride[] {
|
||||
const overloads = [...this.moduleOverloadsMap.entries()];
|
||||
return overloads.map(([moduleToReplace, newModule]) => ({
|
||||
moduleToReplace,
|
||||
newModule,
|
||||
}));
|
||||
}
|
||||
|
||||
private getRootModule() {
|
||||
const modules = this.container.getModules().values();
|
||||
return modules.next().value;
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('WsContextCreator', () => {
|
||||
expect(tryActivateSpy.called).to.be.true;
|
||||
});
|
||||
describe('when can not activate', () => {
|
||||
it('should throws forbidden exception', async () => {
|
||||
it('should throw forbidden exception', async () => {
|
||||
sinon
|
||||
.stub(guardsConsumer, 'tryActivate')
|
||||
.callsFake(async () => false);
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('WsExceptionsHandler', () => {
|
||||
handler.setCustomFilters(filters as any);
|
||||
expect((handler as any).filters).to.be.eql(filters);
|
||||
});
|
||||
it('should throws exception when passed argument is not an array', () => {
|
||||
it('should throw exception when passed argument is not an array', () => {
|
||||
expect(() => handler.setCustomFilters(null)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('WebSocketsController', () => {
|
||||
subscribeToServerEvents = sinon.spy();
|
||||
(instance as any).subscribeToServerEvents = subscribeToServerEvents;
|
||||
});
|
||||
it('should throws "InvalidSocketPortException" when port is not a number', () => {
|
||||
it('should throw "InvalidSocketPortException" when port is not a number', () => {
|
||||
Reflect.defineMetadata(PORT_METADATA, 'test', InvalidGateway);
|
||||
expect(() =>
|
||||
instance.connectGatewayToServer(
|
||||
|
||||
Reference in New Issue
Block a user