generated from Evyweb/node-script-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
294 additions
and
427 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()}`); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); | ||
}); |