Skip to content

Commit

Permalink
refactor(core): enhance dependency handling and error management
Browse files Browse the repository at this point in the history
- Adopt a dependencies container approach over the previous dependencies map.
- Introduce custom Automock errors for better error clarity.
- Refine the `UnitReference` interface for improved type safety.
- Utilize `require` with default in the package resolver.
- Add integration tests to validate new changes.
- Enhance JSDoc comments for better clarity.
- Update terminology: Replace "primitive" with "constant value".
- Remove redundant warning about undefined dependency values.
- Adjust error messages for more accurate feedback on invalid dependencies.
- Introduce error handling for invalid dependency identifiers in `.mock()`.

BREAKING CHANGE: - Transition from native `Error` to custom Automock errors
- Update `UnitReference.get()` to return both `ConstantValue` and `StubbedInstance`.
  • Loading branch information
omermorad committed Nov 4, 2023
1 parent 3940c5b commit ad2ba48
Show file tree
Hide file tree
Showing 19 changed files with 840 additions and 500 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { PackageResolver } from '../src/services/package-resolver';

describe('Automock Adapter Package Resolving Integration Test', () => {
let packageResolver: PackageResolver;

describe('Resolving an adapter with default export', () => {
beforeAll(() => {
packageResolver = new PackageResolver(
{ test: './assets/test-adapter' },
{ resolve: require.resolve, require }
);
});

it('should successfully resolve the adapter package', () => {
const adapter = packageResolver.resolveCorrespondingAdapter();
expect(adapter.inspect({} as never)).toBe('success');
});
});

describe('Resolving an adapter with no default export', () => {
beforeAll(() => {
packageResolver = new PackageResolver(
{ test: './assets/invalid-adapter' },
{ resolve: require.resolve, require }
);
});

it('should failed resolving the adapter package and throw an error', () => {
expect(() => packageResolver.resolveCorrespondingAdapter()).toThrow(
new Error('Adapter has no default export')
);
});
});
});
185 changes: 185 additions & 0 deletions packages/core/__test__/assets/integration.assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import isEqual from 'lodash.isequal';
import {
AutomockDependenciesAdapter,
ClassInjectable,
IdentifierMetadata,
UndefinedDependency,
WithMetadata,
} from '@automock/common';
import { Type } from '@automock/types';
import { normalizeIdentifier } from '../../src/normalize-identifier.stastic';

interface Printer {
print(): string;
}

export class ArbitraryClassOne {
print(): string {
return this.constructor.name;
}
}

export class ArbitraryClassTwo {
print(): string {
return this.constructor.name;
}
}

export class ArbitraryClassThree {
print(): string {
return this.constructor.name;
}
}

export class ArbitraryClassFour {
print(): string {
return this.constructor.name;
}
}

export class ArbitraryClassFive {
print(): string {
return this.constructor.name;
}
}

export class ArbitraryClassSix {
print(): string {
return this.constructor.name;
}
}
export class ArbitraryClassSeven {
print(): string {
return this.constructor.name;
}
}

export class ClassFromToken {
print(): string {
return this.constructor.name;
}
}

export class StubbedClass {
print(): string {
return 'Stubbed';
}
}

/**
* We create the ClassUnderTest with no decorators because the reflection
* is being mocked in this integration test. But what we do want to check
* is that all the class properties have been initiated properly, as well
* as the constructor parameters. The list of injectables from the mocked
* adapter should match exactly the order of the parameters in the constructor,
* as we verify this in the integration test.
*/
export class ClassUnderTest {
public arbitraryThree: ArbitraryClassThree;
public arbitraryFour: ArbitraryClassFour;
public arbitraryProperty: string;
public withMetadata: StubbedClass;
public withMetadataSecond: StubbedClass;
public arbitrary: StubbedClass;

public constructor(
private readonly one: Printer,
private readonly two: Printer,
private readonly twoSecond: Printer,
private readonly five: Printer,
private readonly sixWithToken: Printer,
private readonly tokenUndefined: Printer,
private readonly justToken: Printer
) {}

public getArguments() {
return [
this.one,
this.two,
this.twoSecond,
this.five,
this.sixWithToken,
this.tokenUndefined,
this.justToken,
];
}

public getProperties() {
return Object.keys(this);
}
}

export const symbolIdentifier = Symbol.for('TOKEN_METADATA');

const classInjectables: ClassInjectable[] = [
{ identifier: ArbitraryClassOne, value: ArbitraryClassOne, type: 'PARAM' },
// We put the same class twice to test that the mocker, it is not a mistake
{ identifier: ArbitraryClassTwo, value: ArbitraryClassTwo, type: 'PARAM' },
{ identifier: ArbitraryClassTwo, value: ArbitraryClassTwo, type: 'PARAM' },
{ identifier: ArbitraryClassFive, value: UndefinedDependency, type: 'PARAM' },
{
identifier: 'ArbitraryClassSix',
metadata: { metadataKey: 'value' },
value: ArbitraryClassSix,
type: 'PARAM',
} as WithMetadata<never>,
{ identifier: 'TOKEN_WITH_UNDEFINED', value: UndefinedDependency, type: 'PARAM' },
{ identifier: 'TOKEN', value: ClassFromToken, type: 'PARAM' },
{
type: 'PROPERTY',
property: { key: 'arbitraryThree' },
identifier: ArbitraryClassThree,
value: ArbitraryClassThree,
},
{
type: 'PROPERTY',
property: { key: 'withMetadata' },
metadata: { key: 'value' },
identifier: ArbitraryClassSeven,
value: ArbitraryClassSeven,
} as ClassInjectable,
{
type: 'PROPERTY',
property: { key: 'withMetadataSecond' },
metadata: { key: 'value' },
identifier: symbolIdentifier,
value: ArbitraryClassSeven,
} as ClassInjectable,
{
type: 'PROPERTY',
property: { key: 'arbitraryFour' },
identifier: ArbitraryClassFour,
value: ArbitraryClassFour,
},
{
type: 'PROPERTY',
property: { key: 'arbitraryProperty' },
identifier: 'STRING_TOKEN',
value: String,
},
{
type: 'PROPERTY',
property: { key: 'arbitrary' },
identifier: 'ANOTHER_TOKEN',
value: String,
},
];

export const FakeAdapter: AutomockDependenciesAdapter = {
inspect: () => {
return {
list: () => classInjectables,
resolve(
identifier: Type | string,
metadata?: IdentifierMetadata
): ClassInjectable | undefined {
return classInjectables.find((injectable: WithMetadata<never>) => {
const subject = normalizeIdentifier(identifier, metadata);
const toFind = normalizeIdentifier(injectable.identifier, injectable.metadata);

return isEqual(toFind, subject);
});
},
};
},
};
7 changes: 7 additions & 0 deletions packages/core/__test__/assets/invalid-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AutomockDependenciesAdapter, InjectablesRegistry } from '@automock/common';

export = {
inspect(): InjectablesRegistry {
return 'not-reachable' as unknown as InjectablesRegistry;
},
} as AutomockDependenciesAdapter;
7 changes: 7 additions & 0 deletions packages/core/__test__/assets/test-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AutomockDependenciesAdapter, InjectablesRegistry } from '@automock/common';

export default {
inspect(): InjectablesRegistry {
return 'success' as unknown as InjectablesRegistry;
},
} as AutomockDependenciesAdapter;
42 changes: 0 additions & 42 deletions packages/core/__test__/integration.assets.ts

This file was deleted.

Loading

0 comments on commit ad2ba48

Please sign in to comment.