refactor(di): modularize and improve dependency injection for deno
- Consolidate import paths into scoped modules for better structure. - Refactor decorators (`Inject`, `Register`) for improved type safety. - Add `clear` method to the DI container for easier test cleanup. - Introduce lazy initialization for registered instances. - Add comprehensive unit tests for decorators and DI container. - Standardize error handling and naming conventions for exceptions. Signed-off-by: Max P. <Mail@MPassarello.de>
This commit is contained in:
229
tests/Decorators.ts
Normal file
229
tests/Decorators.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
// deno-coverage-ignore-file
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import {
|
||||
assertEquals,
|
||||
assertInstanceOf,
|
||||
assertStrictEquals,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.224.0/assert/mod.ts";
|
||||
import { TSinjex } from "../src/classes/mod.ts";
|
||||
import { Inject, Register } from "../src/decorators/mod.ts";
|
||||
import { DependencyResolutionError } from "../src/interfaces/mod.ts";
|
||||
|
||||
const container = TSinjex.getInstance() as TSinjex;
|
||||
|
||||
Deno.test("should inject dependency when necessary is true", () => {
|
||||
container.clear();
|
||||
container.register("MockDependencyIdentifier", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject("MockDependencyIdentifier")
|
||||
private _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
assertEquals(instance.getDependency().value, "test-value");
|
||||
});
|
||||
|
||||
Deno.test("should inject dependency and run initializer", () => {
|
||||
container.clear();
|
||||
container.register("MockDependencyIdentifier", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject("MockDependencyIdentifier", (x: any) => {
|
||||
x.value = "test-value-init";
|
||||
return x;
|
||||
})
|
||||
dependency!: any;
|
||||
public getDependency() {
|
||||
return this.dependency;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
assertEquals(instance.getDependency().value, "test-value-init");
|
||||
});
|
||||
|
||||
Deno.test("should throw error if initializer fails and dependency is necessary", () => {
|
||||
container.clear();
|
||||
container.register("InitThrowDependencie", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject<TestClass, any, any>("InitThrowDependencie", () => {
|
||||
throw new Error("Initializer error");
|
||||
}, true)
|
||||
dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this.dependency;
|
||||
}
|
||||
}
|
||||
|
||||
assertThrows(
|
||||
() => {
|
||||
const instance = new TestClass();
|
||||
instance.getDependency()();
|
||||
},
|
||||
Error,
|
||||
"Initializer error",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("should throw DependencyResolutionError if dependency not found", () => {
|
||||
container.clear();
|
||||
|
||||
class TestClass {
|
||||
@Inject("NonExistentDependencyIdentifier")
|
||||
private _dependency!: any;
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
assertThrows(() => {
|
||||
const instance = new TestClass();
|
||||
instance.getDependency()();
|
||||
}, DependencyResolutionError);
|
||||
});
|
||||
|
||||
Deno.test("should replace the property with the resolved dependency", () => {
|
||||
container.clear();
|
||||
container.register("MockDependencyIdentifier", { value: "test-value" });
|
||||
|
||||
class TestClass {
|
||||
@Inject("MockDependencyIdentifier")
|
||||
private _dependency!: any;
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
public isDependencyTypeofFunction() {
|
||||
return typeof this._dependency === "function";
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestClass();
|
||||
assertEquals(instance.getDependency().value, "test-value");
|
||||
assertEquals(instance.isDependencyTypeofFunction(), false);
|
||||
assertEquals(instance.getDependency().value, "test-value");
|
||||
});
|
||||
|
||||
Deno.test("Register Decorator: should register a dependency", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("MockDependencyIdentifier")
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
}
|
||||
|
||||
assertStrictEquals(
|
||||
container.resolve("MockDependencyIdentifier"),
|
||||
TestClass,
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: should register an instance of a dependency", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x) => new x())
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
}
|
||||
|
||||
const resolved = container.resolve<TestClass>("InstanceIdentifier");
|
||||
assertEquals(resolved?.mark, "instance");
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: should run init function during registration", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x: new () => TestClass) => {
|
||||
const instance = new x();
|
||||
instance.mark = "init";
|
||||
return instance;
|
||||
})
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
}
|
||||
|
||||
const resolved = container.resolve<TestClass>("InstanceIdentifier");
|
||||
assertEquals(resolved?.mark, "init");
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: instance should persist modifications", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x) => new x())
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
public test: string = "test";
|
||||
}
|
||||
|
||||
const instance1 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance1 == null) {
|
||||
throw new Error("Instance1 is null");
|
||||
}
|
||||
instance1.test = "test2";
|
||||
|
||||
const instance2 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance2 == null) {
|
||||
throw new Error("Instance2 is null");
|
||||
}
|
||||
assertEquals(instance2.test, "test2");
|
||||
});
|
||||
|
||||
Deno.test("RegisterInstance: init function should persist modifications", () => {
|
||||
container.clear();
|
||||
|
||||
@Register("InstanceIdentifier", (x: new () => TestClass) => {
|
||||
const instance = new x();
|
||||
instance.mark = "init";
|
||||
return instance;
|
||||
})
|
||||
class TestClass {
|
||||
private readonly _dependency!: any;
|
||||
|
||||
public getDependency() {
|
||||
return this._dependency;
|
||||
}
|
||||
|
||||
public mark: string = "instance";
|
||||
public test: string = "test";
|
||||
}
|
||||
|
||||
const instance1 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance1 == null) {
|
||||
throw new Error("Instance1 is null");
|
||||
}
|
||||
instance1.test = "test2";
|
||||
|
||||
const instance2 = container.resolve<TestClass>("InstanceIdentifier");
|
||||
if (instance2 == null) {
|
||||
throw new Error("Instance2 is null");
|
||||
}
|
||||
assertEquals(instance2.test, "test2");
|
||||
});
|
78
tests/TSInjex.ts
Normal file
78
tests/TSInjex.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
// deno-coverage-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
import {
|
||||
assertEquals,
|
||||
assertStrictEquals,
|
||||
assertThrows,
|
||||
} from "https://deno.land/std@0.224.0/assert/mod.ts";
|
||||
import { TSinjex } from "../src/classes/mod.ts";
|
||||
|
||||
const container = TSinjex.getInstance() as TSinjex;
|
||||
|
||||
Deno.test("should register and resolve a dependency (instance)", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "myDependency";
|
||||
const dependency = { value: 42 };
|
||||
|
||||
container.register(identifier, dependency);
|
||||
|
||||
const resolved = container.resolve<typeof dependency>(identifier);
|
||||
assertStrictEquals(resolved, dependency);
|
||||
});
|
||||
|
||||
Deno.test("should register and resolve a dependency (static)", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "myDependency";
|
||||
const dependency = { value: 42 };
|
||||
|
||||
TSinjex.register(identifier, dependency);
|
||||
const resolved = TSinjex.resolve<typeof dependency>(identifier);
|
||||
|
||||
assertStrictEquals(resolved, dependency);
|
||||
});
|
||||
|
||||
Deno.test("should throw error when resolving non-registered dependency (static)", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "nonExistentDependency";
|
||||
assertThrows(() => {
|
||||
TSinjex.resolve<unknown>(identifier);
|
||||
});
|
||||
});
|
||||
|
||||
Deno.test("should return undefined when resolving non-necessary dependency", () => {
|
||||
container.clear();
|
||||
|
||||
const result = TSinjex.resolve<unknown>("nonExistentDependency", false);
|
||||
assertEquals(result, undefined);
|
||||
});
|
||||
|
||||
Deno.test("should warn when resolving a deprecated dependency", () => {
|
||||
container.clear();
|
||||
|
||||
const identifier = "deprecatedDependency";
|
||||
const dependency = { value: 42 };
|
||||
|
||||
// Mock console.warn
|
||||
const originalWarn = console.warn;
|
||||
let wasCalled = false;
|
||||
let lastMessage = "";
|
||||
|
||||
console.warn = (msg: string) => {
|
||||
wasCalled = true;
|
||||
lastMessage = msg;
|
||||
};
|
||||
|
||||
try {
|
||||
TSinjex.register(identifier, dependency, true);
|
||||
const resolved = TSinjex.resolve<typeof dependency>(identifier);
|
||||
assertStrictEquals(resolved, dependency);
|
||||
if (!wasCalled) {
|
||||
throw new Error("console.warn was not called");
|
||||
}
|
||||
} finally {
|
||||
console.warn = originalWarn; // Restore
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user