Skip to content

Commit

Permalink
refactor: reorganize tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Evyweb committed Nov 10, 2024
1 parent 4ef1f51 commit e0b73f5
Show file tree
Hide file tree
Showing 17 changed files with 294 additions and 427 deletions.
441 changes: 15 additions & 426 deletions specs/container.spec.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion specs/DI.ts → specs/examples/DI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {InjectionTokens} from "../src";
import {InjectionTokens} from "../../src";

export const DI: InjectionTokens = {
DEP1: Symbol('DEP1'),
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
129 changes: 129 additions & 0 deletions specs/module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {Container, createContainer, createModule} from "../src";
import {DI} from "./examples/DI";
import {sayHelloWorld} from "./examples/sayHelloWorld";
import {SayHelloType} from "./examples/SayHelloType";
import {MyService} from "./examples/MyService";
import {MyServiceInterface} from "./examples/MyServiceInterface";

describe('Module', () => {

let container: Container;

beforeEach(() => {
container = createContainer();
});

describe('When a module is loaded', () => {
it('should return all module dependencies', () => {
// Arrange
const myModule = createModule();
myModule.bind(DI.SIMPLE_FUNCTION).toFunction(sayHelloWorld);
container.load(Symbol('myModule'), myModule);

// Act
const sayHello = container.get<SayHelloType>(DI.SIMPLE_FUNCTION);

// Assert
expect(sayHello()).toBe('hello world');
});

describe('When a dependency of the module is registered in another module', () => {
it('should correctly resolve all dependencies', () => {
// Arrange
const module1 = createModule();
module1.bind(DI.DEP1).toValue('dependency1');

const module2 = createModule();
module2.bind(DI.DEP2).toValue(42);

const module3 = createModule();
module3.bind(DI.MY_SERVICE).toHigherOrderFunction(MyService, {dep1: DI.DEP1, dep2: DI.DEP2});

container.load(Symbol('module1'), module1);
container.load(Symbol('module2'), module2);
container.load(Symbol('module3'), module3);

// Act
const myService = container.get<MyServiceInterface>(DI.MY_SERVICE);

// Assert
expect(myService.runTask()).toBe('Executing with dep1: dependency1 and dep2: 42');
});

it('should take the last registered values', () => {
// Arrange
const module1 = createModule();
module1.bind(DI.DEP1).toValue('OLD dependency1');
module1.bind(DI.MY_SERVICE).toFunction(sayHelloWorld);

const module2 = createModule();
module2.bind(DI.DEP1).toValue('NEW dependency1');

const module3 = createModule();
module3.bind(DI.MY_SERVICE).toHigherOrderFunction(MyService, {dep1: DI.DEP1, dep2: DI.DEP2});

container.bind(DI.DEP2).toValue(42);
container.load(Symbol('module1'), module1);
container.load(Symbol('module2'), module2);
container.load(Symbol('module3'), module3);

// Act
const myService = container.get<MyServiceInterface>(DI.MY_SERVICE);

// Assert
expect(myService.runTask()).toBe('Executing with dep1: NEW dependency1 and dep2: 42');
});
});
});

describe('When a module is unloaded', () => {
describe('When another module has this dependency already registered', () => {
it('should use the existing dependency', () => {
// Arrange
const MODULE1 = Symbol('myModule1');
const MODULE2 = Symbol('myModule2');

const module1 = createModule();
module1.bind(DI.SIMPLE_FUNCTION).toFunction(() => {
return 'module 1 hello world';
});
container.load(MODULE1, module1);

const module2 = createModule();
module2.bind(DI.SIMPLE_FUNCTION).toFunction(sayHelloWorld);
container.load(MODULE2, module2);

const sayHelloBeforeUnload = container.get<SayHelloType>(DI.SIMPLE_FUNCTION);
expect(sayHelloBeforeUnload()).toBe('hello world');

// Act
container.unload(MODULE2);

// Assert
const sayHelloAfterUnload = container.get<SayHelloType>(DI.SIMPLE_FUNCTION);
expect(sayHelloAfterUnload()).toBe('module 1 hello world');
});
});

describe('When no other module has this dependency already registered', () => {
it('should remove all its dependencies', () => {
// Arrange
const MY_MODULE = Symbol('myModule');

const module = createModule();
module.bind(DI.SIMPLE_FUNCTION).toFunction(sayHelloWorld);
container.load(MY_MODULE, module);

const sayHelloBeforeUnload = container.get<SayHelloType>(DI.SIMPLE_FUNCTION);
expect(sayHelloBeforeUnload()).toBe('hello world');

// Act
container.unload(MY_MODULE);

// Assert
expect(() => container.get<SayHelloType>(DI.SIMPLE_FUNCTION))
.toThrowError(`No binding found for key: ${DI.SIMPLE_FUNCTION.toString()}`);
});
});
});
});
149 changes: 149 additions & 0 deletions specs/scope.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {Container, createContainer, Scope} from "../src";
import {DI} from "./examples/DI";
import {MyService} from "./examples/MyService";
import {MyServiceInterface} from "./examples/MyServiceInterface";
import {vi} from "vitest";
import {sayHelloWorld} from "./examples/sayHelloWorld";
import {SayHelloType} from "./examples/SayHelloType";

describe('Scope', () => {

let container: Container;
let factoryCalls = vi.fn();

beforeEach(() => {
container = createContainer();
container.bind(DI.DEP1).toValue('dependency1');
container.bind(DI.DEP2).toValue(42);
factoryCalls = vi.fn();
});

describe.each([
{scope: undefined},
{scope: 'singleton'},
])('When the scope is default or defined to "singleton"', ({scope}) => {
it('should return the same instance', () => {
// Arrange
container.bind(DI.MY_SERVICE).toFactory(() => {
factoryCalls();
return MyService({
dep1: container.get<string>(DI.DEP1),
dep2: container.get<number>(DI.DEP2)
});
}, scope as Scope);

const myService1 = container.get<MyServiceInterface>(DI.MY_SERVICE);

// Act
const myService2 = container.get<MyServiceInterface>(DI.MY_SERVICE);

// Assert
expect(myService1).toBe(myService2);
expect(factoryCalls).toHaveBeenCalledTimes(1);
});
});

describe('When the scope is defined to "transient"', () => {
it('should return a new instance each time', () => {
// Arrange
container.bind(DI.MY_SERVICE).toFactory(() => {
factoryCalls();
return MyService({
dep1: container.get<string>(DI.DEP1),
dep2: container.get<number>(DI.DEP2)
});
}, 'transient');

const myService1 = container.get<MyServiceInterface>(DI.MY_SERVICE);

// Act
const myService2 = container.get<MyServiceInterface>(DI.MY_SERVICE);

// Assert
expect(myService1).not.toBe(myService2);
expect(factoryCalls).toHaveBeenCalledTimes(2);
});
});

describe('When the scope is defined to "scoped"', () => {
it('should return the same instance within the same scope', () => {
// Arrange
container.bind(DI.DEP1).toValue('dependency1');
container.bind(DI.DEP2).toValue(42);
container.bind(DI.MY_SERVICE).toFactory(() => {
factoryCalls();
return MyService({
dep1: container.get<string>(DI.DEP1),
dep2: container.get<number>(DI.DEP2)
});
}, 'scoped');

let myService1: MyServiceInterface | undefined;
let myService2: MyServiceInterface | undefined;

// Act
container.runInScope(() => {
myService1 = container.get<MyServiceInterface>(DI.MY_SERVICE);
myService2 = container.get<MyServiceInterface>(DI.MY_SERVICE);
});

// Assert
expect(myService1).toBeDefined();
expect(myService2).toBeDefined();
expect(myService1).toBe(myService2);
expect(factoryCalls).toHaveBeenCalledTimes(1);
});

it('should return different instances in different scopes', () => {
// Arrange
container.bind(DI.MY_SERVICE).toFactory(() => {
factoryCalls();
return MyService({
dep1: container.get<string>(DI.DEP1),
dep2: container.get<number>(DI.DEP2)
});
}, 'scoped');

let myService1: MyServiceInterface | undefined;
let myService2: MyServiceInterface | undefined;

container.runInScope(() => {
myService1 = container.get<MyServiceInterface>(DI.MY_SERVICE);
});

// Act
container.runInScope(() => {
myService2 = container.get<MyServiceInterface>(DI.MY_SERVICE);
});

// Assert
expect(myService1).toBeDefined();
expect(myService2).toBeDefined();
expect(myService1).not.toBe(myService2);
expect(factoryCalls).toHaveBeenCalledTimes(2);
});
});

describe('When a scoped dependency is resolved outside of a scope', () => {
it('should throw an error', () => {
// Arrange
container.bind(DI.MY_SERVICE)
.toHigherOrderFunction(MyService, {dep1: DI.DEP1, dep2: DI.DEP2}, 'scoped');

// Act & Assert
expect(() => container.get<MyServiceInterface>(DI.MY_SERVICE))
.toThrowError(`Cannot resolve scoped binding outside of a scope: ${DI.MY_SERVICE.toString()}`);
});
});

describe('When an unknown scope is used during binding', () => {
it('should throw an error', () => {
// Arrange
container.bind(DI.MY_SERVICE).toFunction(sayHelloWorld, 'unknown' as any);

// Act & Assert
expect(() => container.get<SayHelloType>(DI.MY_SERVICE))
.toThrowError('Unknown scope: unknown');
});
});
});

0 comments on commit e0b73f5

Please sign in to comment.