@nivinjoseph/n-ject
Version:
IOC container
484 lines (398 loc) • 14.5 kB
text/typescript
import { given } from "@nivinjoseph/n-defensive";
import assert from "node:assert";
import { afterEach, beforeEach, describe, test } from "node:test";
import { ComponentInstaller, Container, Registry, inject } from "./../src/index.js";
await describe("Container", async () =>
{
let cont: Container;
beforeEach(() =>
{
cont = new Container();
});
afterEach(async () =>
{
await cont.dispose();
});
await describe("Installer check", async () =>
{
class A { }
class TestInstaller implements ComponentInstaller
{
public install(registry: Registry): void
{
registry.registerTransient("a", A);
}
}
await test("should resolve successfully when using the installer based registration", () =>
{
const inst = new TestInstaller();
cont.install(inst);
cont.bootstrap();
const a = cont.resolve("a");
assert.notStrictEqual(a, null);
});
});
await describe("Bootstrap check", async () =>
{
class A { }
await test("should throw exception when creating a child scope before bootstrapping", () =>
{
assert.throws(() =>
{
cont.createScope();
});
});
await test("should throw exception when registering after bootstrapping", () =>
{
cont.bootstrap();
assert.throws(() =>
{
cont.registerTransient("a", A);
});
});
await test("should throw exception when installing installer after bootstrapping", () =>
{
class TestInstaller implements ComponentInstaller
{
public install(registry: Registry): void
{
registry.registerTransient("a", A);
}
}
const inst = new TestInstaller();
cont.bootstrap();
assert.throws(() =>
{
cont.install(inst);
});
});
await test("should throw exception when resolving unregistered key", () =>
{
cont.bootstrap();
assert.throws(() =>
{
cont.resolve("a");
});
});
await test("should throw except when resolving before bootstrapping", () =>
{
cont.registerTransient("a", A);
assert.throws(() =>
{
cont.resolve("a");
});
});
});
await describe("Resolution Rules", async () =>
{
class B { }
class A
{
public constructor(b: B)
{
given(b, "b").ensureHasValue().ensureIsType(B);
}
}
await describe("Singleton", async () =>
{
beforeEach(() =>
{
cont.registerSingleton("a", A);
});
// singleton -> singleton
await describe("given singleton A to singleton B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerSingleton("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should resolve successfully from root scope", () =>
{
cont.bootstrap();
const a = cont.resolve("a");
assert.notStrictEqual(a, null);
});
});
// singleton -> scoped
await describe("given singleton A to scoped B dependency", async () =>
{
beforeEach(() =>
{
cont.registerScoped("b", B);
});
await test("should throw exception when bootstraping", () =>
{
assert.throws(() =>
{
cont.bootstrap();
});
});
});
// singleton -> transient
await describe("given singleton A to transient B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerTransient("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should resolve successfully from root scope", () =>
{
cont.bootstrap();
const a = cont.resolve("a");
assert.notStrictEqual(a, null);
});
});
});
await describe("Scoped", async () =>
{
beforeEach(() =>
{
cont.registerScoped("a", A);
});
// scoped -> singleton
await describe("given scoped A to singleton B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerSingleton("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should throw exception when resolving from root scope", () =>
{
assert.throws(() =>
{
cont.bootstrap();
cont.resolve("a");
});
});
});
// scoped -> scoped
await describe("given scoped A to scoped B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerScoped("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should throw exception when resolving from root scope", () =>
{
assert.throws(() =>
{
cont.bootstrap();
cont.resolve("a");
});
});
});
// scoped -> transient
await describe("given scoped A to transient B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerTransient("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should throw exception when resolving from root scope", () =>
{
assert.throws(() =>
{
cont.bootstrap();
cont.resolve("a");
});
});
});
});
await describe("Transient", async () =>
{
beforeEach(() =>
{
cont.registerTransient("a", A);
});
// transient -> singleton
await describe("given transient A to singleton B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerSingleton("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should resolve successfully from root scope", () =>
{
cont.bootstrap();
const a = cont.resolve("a");
assert.notStrictEqual(a, null);
});
});
// transient -> scoped
await describe("given transient A to scoped B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerScoped("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should throw exception when resolving from root scope", () =>
{
assert.throws(() =>
{
cont.bootstrap();
cont.resolve("a");
});
});
});
// transient -> transient
await describe("given transient A to transient B dependency, when resolving A", async () =>
{
beforeEach(() =>
{
cont.registerTransient("b", B);
});
await test("should resolve successfully from child scope", () =>
{
cont.bootstrap();
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should resolve successfully from root scope", () =>
{
cont.bootstrap();
const a = cont.resolve("a");
assert.notStrictEqual(a, null);
});
});
});
});
await describe("Instance Check", async () =>
{
class A { }
await describe("Given Singleton A", async () =>
{
beforeEach(() =>
{
cont.registerSingleton("a", A);
cont.bootstrap();
});
await test("should resolve successfully from child scope", async () =>
{
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should resolve successfully from root scope", () =>
{
const a = cont.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should be the same instance when resolved from root scope or any child scope", () =>
{
const child = cont.createScope();
assert.strictEqual(cont.resolve("a"), child.resolve("a"));
});
});
await describe("Given Scoped A", async () =>
{
beforeEach(() =>
{
cont.registerScoped("a", A);
cont.bootstrap();
});
await test("should resolve successfully from the child scope", () =>
{
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should throw exception when resolving the root scope", () =>
{
assert.throws(() =>
{
cont.resolve("a");
});
});
await test("should always return the same instance from same child", () =>
{
const child = cont.createScope();
assert.strictEqual(child.resolve("a"), child.resolve("a"));
});
await test("should always return different instances from different child", () =>
{
const child1 = cont.createScope();
const child2 = cont.createScope();
assert.notStrictEqual(child1.resolve("a"), child2.resolve("a"));
});
});
await describe("Given Transient A", async () =>
{
beforeEach(() =>
{
cont.registerTransient("a", A);
cont.bootstrap();
});
await test("should resolve successfully from the child scope", () =>
{
const child = cont.createScope();
const a = child.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should resolve successfully from the root scope", () =>
{
const a = cont.resolve("a");
assert.notStrictEqual(a, null);
});
await test("should be a new instance every time when resolved multiple times from root scope", () =>
{
assert.notStrictEqual(cont.resolve("a"), cont.resolve("a"));
});
await test("should be a new instance every time when resolved multiple times from child scope", () =>
{
const child = cont.createScope();
assert.notStrictEqual(cont.resolve("a"), child.resolve("a"));
});
});
});
});