Skip to content
This repository has been archived by the owner on Oct 18, 2022. It is now read-only.

Commit

Permalink
wip: adding test for overrideDependency and more detailed typings to …
Browse files Browse the repository at this point in the history
…DependencyContainer
  • Loading branch information
ben-ryder committed Jun 2, 2022
1 parent 082ff95 commit 22d144f
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 18 deletions.
46 changes: 28 additions & 18 deletions packages/core/src/utils/dependency-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@ import {Logger} from "./logger";
import {LoggerBase} from "../types/logger-interface";


/**
* An interface for something that is instantiable.
*/
interface Instantiable<T> {
new(...args: any[]): T;
}

/**
* The interface for a single dependency
*/
export interface StoredDependency {
signature: any,
instance?: any,
export interface StoredDependency<T> {
signature: Instantiable<T>,
instance?: T,
}

/**
* The interface for the dependency store itself.
*/
export interface DependencyStore {
[key: symbol]: StoredDependency;
[key: symbol]: StoredDependency<any>;
}

/**
Expand All @@ -39,15 +46,15 @@ export class DependencyContainer {
* Get the dependency key attached to the metadata if it exists.
* @param dependency
*/
getDependencyKey(dependency: any): symbol | undefined {
getDependencyKey<T>(dependency: Instantiable<T>): symbol | undefined {
return <symbol> Reflect.getMetadata(MetadataKeys.DEPENDENCY_KEY, dependency.prototype);
}

/**
* Get the dependency config attached to the metadata if it exists.
* @param dependency
*/
getDependencyConfig(dependency: any): DependencyConfig | undefined {
getDependencyConfig<T>(dependency: Instantiable<T>): DependencyConfig | undefined {
return <DependencyConfig> Reflect.getMetadata(MetadataKeys.DEPENDENCY_CONFIG, dependency.prototype);
}

Expand All @@ -57,7 +64,7 @@ export class DependencyContainer {
*
* @param dependency
*/
useDependency<T>(dependency: any): T {
useDependency<T>(dependency: Instantiable<T>): T {
const dependencyKey = this.getDependencyKey(dependency);

// To prevent possible issues only accept dependencies that are explicitly marked as injectable
Expand All @@ -67,7 +74,7 @@ export class DependencyContainer {

// Register the dependency if it doesn't already exist.
if (!(dependencyKey in this.dependencyStore)) {
this.registerDependency(dependencyKey, dependency);
this.registerDependency<T>(dependencyKey, dependency);
}

return this.returnDependency<T>(dependencyKey);
Expand All @@ -80,11 +87,11 @@ export class DependencyContainer {
* @param dependency
* @private
*/
private registerDependency(dependencyKey: symbol, dependency: any) {
private registerDependency<T>(dependencyKey: symbol, dependency: Instantiable<T>) {
const dependencyConfig = this.getDependencyConfig(dependency);

if (dependencyConfig?.injectMode === "singleton") {
this.createInstance(dependencyKey, dependency);
this.createInstance<T>(dependencyKey, dependency);
}
else {
this.dependencyStore[dependencyKey] = {
Expand All @@ -104,9 +111,9 @@ export class DependencyContainer {
* @private
* @param dependencyKey
*/
private returnDependency<T>(dependencyKey: any): T {
private returnDependency<T>(dependencyKey: symbol): T {
const storedDependency = this.dependencyStore[dependencyKey];
const storedDependencyConfig = this.getDependencyConfig(storedDependency.signature);
const storedDependencyConfig = this.getDependencyConfig<T>(storedDependency.signature);

if (storedDependencyConfig?.injectMode === "singleton") {
this.logger.log({
Expand All @@ -133,7 +140,7 @@ export class DependencyContainer {
* @param dependencyKey
* @private
*/
private createInstance(dependencyKey: symbol, dependency: any) {
private createInstance<T>(dependencyKey: symbol, dependency: Instantiable<T>) {
const constructorArguments: any[] = [];
const argumentTypes = <any[]> Reflect.getMetadata("design:paramtypes", dependency) ?? [];

Expand All @@ -145,7 +152,7 @@ export class DependencyContainer {
}
else {
constructorArguments.push(
this.useDependency(dependency)
this.useDependency<T>(dependency)
);
}
}
Expand All @@ -165,12 +172,15 @@ export class DependencyContainer {
* Override the given dependency with a new one.
* This can be used for overriding default dependencies for things like testing or custom implementations.
*
* // todo: is there a way to make this properly type safe?
* so ideally you would only be able to pass a dependencyOverride that matches types with dependency
*
* @param dependency
* @param dependencyOverride
*/
overwriteDependency(dependency: any, dependencyOverride: any) {
const dependencyKey = this.getDependencyKey(dependency);
const dependencyOverrideConfig = this.getDependencyConfig(dependencyOverride);
overrideDependency<T>(dependency: Instantiable<T>, dependencyOverride: Instantiable<T>) {
const dependencyKey = this.getDependencyKey<T>(dependency);
const dependencyOverrideConfig = this.getDependencyConfig<T>(dependencyOverride);

if (!dependencyKey) {
throw new Error("You can't override a dependency that isn't marked as injectable as it can never be used");
Expand All @@ -179,6 +189,6 @@ export class DependencyContainer {
throw new Error("You can't override a dependency with one that isn't marked as injectable");
}

this.registerDependency(dependencyKey, dependencyOverride);
this.registerDependency<T>(dependencyKey, dependencyOverride);
}
}
11 changes: 11 additions & 0 deletions packages/core/tests/dependency-container.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class TestSingletonDependency {
}
}

class ChildTestSingletonDependency extends TestSingletonDependency{}

@Injectable({
injectMode: "unique",
identifier: "test-unique"
Expand All @@ -25,6 +27,7 @@ class TestUniqueDependency {
}
}


describe("Dependency Container",() => {
let testDependencyContainer: DependencyContainer;

Expand All @@ -51,4 +54,12 @@ describe("Dependency Container",() => {

expect(dep1 === dep2).toBeFalsy();
});

it("When a dependency is overridden, the new dependency override should then be returned", async () => {
testDependencyContainer.useDependency(TestSingletonDependency);
testDependencyContainer.overrideDependency(TestSingletonDependency, ChildTestSingletonDependency);
const dep2 = testDependencyContainer.useDependency<ChildTestSingletonDependency>(TestSingletonDependency);

expect(dep2).toBeInstanceOf(ChildTestSingletonDependency);
});
});

0 comments on commit 22d144f

Please sign in to comment.