diff --git a/src/lib/reflector.service.spec.ts b/src/lib/reflector.service.spec.ts index 41dd0b3d..f8749dab 100644 --- a/src/lib/reflector.service.spec.ts +++ b/src/lib/reflector.service.spec.ts @@ -34,7 +34,7 @@ describe('Reflector Service TestBed', () => { describe('scenario: successfully reflecting dependencies and tokens', () => { describe('when not overriding any of the class dependencies', () => { - let result: Map, Type>; + let result: Map, string | Type>; beforeAll(() => { getMetadataStub.mockImplementation(VALID_IMPL); diff --git a/src/lib/reflector.service.ts b/src/lib/reflector.service.ts index 52895870..b600faea 100644 --- a/src/lib/reflector.service.ts +++ b/src/lib/reflector.service.ts @@ -5,42 +5,46 @@ export interface CustomToken { param: Type | { forwardRef: () => Type } | string; } +export type TokenOrType = string | Type; +export type ClassDependencies = Map>; + export class ReflectorService { private static readonly INJECTED_TOKENS_METADATA = 'self:paramtypes'; private static readonly PARAM_TYPES_METADATA = 'design:paramtypes'; public constructor(private readonly reflector: typeof Reflect) {} - public reflectDependencies(targetClass: Type): Map, Type> { - const classDependencies = new Map, Type>(); + public reflectDependencies(targetClass: Type): ClassDependencies { + const classDependencies = new Map>(); const types = this.reflectParamTypes(targetClass); const tokens = this.reflectParamTokens(targetClass); - const duplicates = ReflectorService.findDuplicates([...types]).map((typeOrToken) => - typeof typeOrToken === 'string' ? typeOrToken : typeOrToken.name - ); - - types.forEach((type: Type, index: number) => { - if (type.name === 'Object' || duplicates.includes(type.name)) { - const token = ReflectorService.findToken(tokens, index); - - if (!token) { - if (type.name === 'Object') { - throw new Error( - `'${targetClass.name}' is missing a token for the dependency at index [${index}], did you forget to inject it using @Inject()?` - ); - } else { - throw new Error(`'${targetClass.name}' includes non-unique types/tokens dependencies`); + types.forEach((type, index) => { + const token = ReflectorService.findToken(tokens, index); + const isObjectType = type && type.name === 'Object'; + + if (token) { + const ref = ReflectorService.resolveRefFromToken(token); + if (isObjectType) { + if (typeof ref !== 'string') { + classDependencies.set(ref, ref); + return; } } - - const ref = typeof token === 'object' && 'forwardRef' in token ? token.forwardRef() : token; - - classDependencies.set(ref, type); - } else { + if (type) { + classDependencies.set(ref, type); + return; + } + } + if (type && !isObjectType) { classDependencies.set(type, type); + return; } + + throw new Error( + `'${targetClass.name}' is missing a token for the dependency at index [${index}], did you forget to inject it using @Inject()?` + ); }); return classDependencies; @@ -50,31 +54,19 @@ export class ReflectorService { return this.reflector.getMetadata(ReflectorService.INJECTED_TOKENS_METADATA, targetClass) || []; } - private reflectParamTypes(targetClass: Type): Type[] { + private reflectParamTypes(targetClass: Type): Array { return this.reflector.getMetadata(ReflectorService.PARAM_TYPES_METADATA, targetClass) || []; } - private static findToken( - list: CustomToken[], - index: number - ): Type | { forwardRef: () => Type } | string | undefined { + private static findToken(list: CustomToken[], index: number): Token | undefined { const record = list.find((element) => element.index === index); return record?.param; } - private static findDuplicates(typesOrToken: (Type | string)[]) { - const items = [...typesOrToken.sort()]; - - let index = items.length; - const duplicates = []; - - while (index--) { - items[index] === items[index - 1] && - duplicates.indexOf(items[index]) == -1 && - duplicates.push(items[index]); - } - - return duplicates; + private static resolveRefFromToken(token: Token): string | Type { + return typeof token === 'object' && 'forwardRef' in token ? token.forwardRef() : token; } } + +type Token = Type | { forwardRef: () => Type } | string; diff --git a/src/lib/test-bed-resolver.ts b/src/lib/test-bed-resolver.ts index 0f659a24..a6d9f816 100644 --- a/src/lib/test-bed-resolver.ts +++ b/src/lib/test-bed-resolver.ts @@ -2,7 +2,7 @@ import 'reflect-metadata'; import { DeepPartial } from 'ts-essentials'; import { MockFunction, Override, UnitTestBed, Type } from './types'; import { MockResolver } from './mock-resolver'; -import { ReflectorService } from './reflector.service'; +import { ReflectorService, TokenOrType } from './reflector.service'; import Mocked = jest.Mocked; @@ -28,7 +28,7 @@ export interface TestBedResolver { } export class TestBedResolver { - private readonly dependencies = new Map>(); + private readonly dependencies = new Map>(); private readonly depNamesToMocks = new Map>(); public constructor( diff --git a/test/automock-jest-nestjs-e2e.test.ts b/test/automock-jest-nestjs-e2e.test.ts index 88324883..70235f64 100644 --- a/test/automock-jest-nestjs-e2e.test.ts +++ b/test/automock-jest-nestjs-e2e.test.ts @@ -15,20 +15,22 @@ describe('AutoMock NestJS E2E Test', () => { let unitResolver: TestBedResolver; describe('given a unit testing builder with two overrides', () => { + const loggerMock = { + log() { + return 'baz-from-test'; + }, + }; + const testClassOneMock: { foo?: ((flag: boolean) => Promise) | undefined; } = { + async foo(): Promise { + return 'foo-from-test'; + }, + }; beforeAll(() => { unitResolver = TestBed.create(NestJSTestClass) .mock(TestClassOne) - .using({ - async foo(): Promise { - return 'foo-from-test'; - }, - }) + .using(testClassOneMock) .mock('LOGGER') - .using({ - log() { - return 'baz-from-test'; - }, - }); + .using(loggerMock); }); describe('when compiling the builder and turning into testing unit', () => { @@ -42,11 +44,11 @@ describe('AutoMock NestJS E2E Test', () => { test('then successfully resolve the dependencies of the tested classes', () => { const { unitRef } = unit; - expect(unitRef.get(TestClassOne)).toBeDefined(); + expect(unitRef.get(TestClassOne).foo).toBe(testClassOneMock.foo); expect(unitRef.get(TestClassTwo)).toBeDefined(); expect(unitRef.get(getRepositoryToken(Foo) as string)).toBeDefined(); expect(unitRef.get(getRepositoryToken(Bar) as string)).toBeDefined(); - expect(unitRef.get('LOGGER')).toBeDefined(); + expect(unitRef.get<{ log: () => void }>('LOGGER').log).toBe(loggerMock.log); expect(unitRef.get(TestClassThree)).toBeDefined(); });