1 |
rakinar2 |
575 |
import "reflect-metadata"; |
2 |
|
|
|
3 |
|
|
import Container from "@framework/container/Container"; |
4 |
|
|
import crypto from "crypto"; |
5 |
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest"; |
6 |
|
|
|
7 |
|
|
describe("container bindings", () => { |
8 |
|
|
let container: Container; |
9 |
|
|
|
10 |
|
|
beforeEach(() => { |
11 |
|
|
container = Container.getInstance(); |
12 |
|
|
}); |
13 |
|
|
|
14 |
|
|
afterEach(() => { |
15 |
|
|
Container.destroyGlobalContainer(); |
16 |
|
|
}); |
17 |
|
|
|
18 |
|
|
it("should bind and resolve a class instance", () => { |
19 |
|
|
class MyClass {} |
20 |
|
|
|
21 |
|
|
container.bind(MyClass); |
22 |
|
|
const instance = container.resolveByClass(MyClass); |
23 |
|
|
|
24 |
|
|
expect(instance).toBeInstanceOf(MyClass); |
25 |
|
|
}); |
26 |
|
|
|
27 |
|
|
it("should bind and resolve a singleton class instance", () => { |
28 |
|
|
class MyClass {} |
29 |
|
|
|
30 |
|
|
container.bind(MyClass, { singleton: true }); |
31 |
|
|
const instance1 = container.resolveByClass(MyClass); |
32 |
|
|
const instance2 = container.resolveByClass(MyClass); |
33 |
|
|
|
34 |
|
|
expect(instance1).toBeInstanceOf(MyClass); |
35 |
|
|
expect(instance2).toBe(instance1); |
36 |
|
|
}); |
37 |
|
|
|
38 |
|
|
it("should bind and resolve a class instance with a custom key", () => { |
39 |
|
|
class MyClass {} |
40 |
|
|
|
41 |
|
|
container.bind(MyClass, { key: "myClass", singleton: true }); |
42 |
|
|
|
43 |
|
|
const instance = container.resolve("myClass"); |
44 |
|
|
expect(instance).toBeInstanceOf(MyClass); |
45 |
|
|
|
46 |
|
|
const instance2 = container.resolveByClass(MyClass); |
47 |
|
|
expect(instance2).toBeInstanceOf(MyClass); |
48 |
|
|
|
49 |
|
|
expect(instance2).toBe(instance); |
50 |
|
|
}); |
51 |
|
|
|
52 |
|
|
it("should implicitly resolve a class instance if not bound", () => { |
53 |
|
|
class MyClass {} |
54 |
|
|
|
55 |
|
|
const instance = container.resolveByClass(MyClass); |
56 |
|
|
expect(instance).toBeInstanceOf(MyClass); |
57 |
|
|
}); |
58 |
|
|
}); |
59 |
|
|
|
60 |
|
|
describe("container bindings with dependencies", () => { |
61 |
|
|
let container: Container; |
62 |
|
|
|
63 |
|
|
beforeEach(() => { |
64 |
|
|
container = Container.getInstance(); |
65 |
|
|
}); |
66 |
|
|
|
67 |
|
|
afterEach(() => { |
68 |
|
|
Container.destroyGlobalContainer(); |
69 |
|
|
}); |
70 |
|
|
|
71 |
|
|
it("should bind and resolve a class instance with dependencies", () => { |
72 |
|
|
class MyClass {} |
73 |
|
|
|
74 |
|
|
class MyDependentClass { |
75 |
|
|
public constructor(public myClass: MyClass) {} |
76 |
|
|
} |
77 |
|
|
|
78 |
|
|
Reflect.defineMetadata("design:paramtypes", [MyClass], MyDependentClass); |
79 |
|
|
|
80 |
|
|
container.bind(MyClass); |
81 |
|
|
container.bind(MyDependentClass); |
82 |
|
|
|
83 |
|
|
const instance = container.resolveByClass(MyDependentClass); |
84 |
|
|
|
85 |
|
|
expect(instance).toBeInstanceOf(MyDependentClass); |
86 |
|
|
expect(instance.myClass).toBeInstanceOf(MyClass); |
87 |
|
|
}); |
88 |
|
|
|
89 |
|
|
it("should bind and resolve a class instance with singleton dependencies", () => { |
90 |
|
|
class MyClass {} |
91 |
|
|
class MyClass2 {} |
92 |
|
|
|
93 |
|
|
class MyDependentClass { |
94 |
|
|
public constructor( |
95 |
|
|
public myClass: MyClass, |
96 |
|
|
public myClass2: MyClass2 |
97 |
|
|
) {} |
98 |
|
|
} |
99 |
|
|
|
100 |
|
|
Reflect.defineMetadata("design:paramtypes", [MyClass, MyClass2], MyDependentClass); |
101 |
|
|
|
102 |
|
|
container.bind(MyClass, { singleton: true }); |
103 |
|
|
container.bind(MyClass2, { singleton: true }); |
104 |
|
|
container.bind(MyDependentClass, { singleton: true }); |
105 |
|
|
|
106 |
|
|
const instance1 = container.resolveByClass(MyDependentClass); |
107 |
|
|
const instance2 = container.resolveByClass(MyDependentClass); |
108 |
|
|
|
109 |
|
|
expect(instance1).toBeInstanceOf(MyDependentClass); |
110 |
|
|
expect(instance2).toBe(instance1); |
111 |
|
|
expect(instance1.myClass).toBeInstanceOf(MyClass); |
112 |
|
|
expect(instance2.myClass).toBe(instance1.myClass); |
113 |
|
|
expect(instance1.myClass2).toBeInstanceOf(MyClass2); |
114 |
|
|
expect(instance2.myClass2).toBe(instance1.myClass2); |
115 |
|
|
}); |
116 |
|
|
|
117 |
|
|
it("should bind and resolve a class instance with deep and circular dependencies", () => { |
118 |
|
|
class Level0Dep0 {} |
119 |
|
|
class Level0Dep1 {} |
120 |
|
|
|
121 |
|
|
class Level1Dep0 { |
122 |
|
|
public constructor(public level0Dep0: Level0Dep0) {} |
123 |
|
|
} |
124 |
|
|
|
125 |
|
|
Reflect.defineMetadata("design:paramtypes", [Level0Dep0], Level1Dep0); |
126 |
|
|
|
127 |
|
|
class Level1Dep1 { |
128 |
|
|
public constructor( |
129 |
|
|
public level0Dep0: Level0Dep0, |
130 |
|
|
public level0Dep1: Level0Dep1 |
131 |
|
|
) {} |
132 |
|
|
} |
133 |
|
|
|
134 |
|
|
Reflect.defineMetadata("design:paramtypes", [Level0Dep0, Level0Dep1], Level1Dep1); |
135 |
|
|
|
136 |
|
|
class Level2Dep0 { |
137 |
|
|
public constructor( |
138 |
|
|
public level1Dep0: Level1Dep0, |
139 |
|
|
public level0Dep1: Level0Dep1 |
140 |
|
|
) {} |
141 |
|
|
} |
142 |
|
|
|
143 |
|
|
Reflect.defineMetadata("design:paramtypes", [Level1Dep0, Level0Dep1], Level2Dep0); |
144 |
|
|
|
145 |
|
|
class Level2Dep1 { |
146 |
|
|
public constructor( |
147 |
|
|
public level1Dep0: Level1Dep0, |
148 |
|
|
public level0Dep0: Level0Dep0, |
149 |
|
|
public level1Dep1: Level1Dep1 |
150 |
|
|
) {} |
151 |
|
|
} |
152 |
|
|
|
153 |
|
|
Reflect.defineMetadata( |
154 |
|
|
"design:paramtypes", |
155 |
|
|
[Level1Dep0, Level0Dep0, Level1Dep1], |
156 |
|
|
Level2Dep1 |
157 |
|
|
); |
158 |
|
|
|
159 |
|
|
class Application { |
160 |
|
|
public constructor( |
161 |
|
|
public level2Dep1: Level2Dep1, |
162 |
|
|
public level2Dep0: Level2Dep0 |
163 |
|
|
) {} |
164 |
|
|
} |
165 |
|
|
|
166 |
|
|
Reflect.defineMetadata("design:paramtypes", [Level2Dep1, Level2Dep0], Application); |
167 |
|
|
|
168 |
|
|
container.bind(Level0Dep0, { singleton: true }); |
169 |
|
|
container.bind(Level0Dep1, { singleton: true }); |
170 |
|
|
container.bind(Level1Dep0, { singleton: true }); |
171 |
|
|
container.bind(Level1Dep1, { singleton: true }); |
172 |
|
|
container.bind(Level2Dep0, { singleton: true }); |
173 |
|
|
container.bind(Level2Dep1, { singleton: true }); |
174 |
|
|
container.bind(Application); |
175 |
|
|
|
176 |
|
|
const instance = container.resolveByClass(Application); |
177 |
|
|
|
178 |
|
|
expect(instance).toBeInstanceOf(Application); |
179 |
|
|
|
180 |
|
|
expect(instance.level2Dep0).toBeInstanceOf(Level2Dep0); |
181 |
|
|
expect(instance.level2Dep1).toBeInstanceOf(Level2Dep1); |
182 |
|
|
|
183 |
|
|
expect(instance.level2Dep0.level1Dep0).toBeInstanceOf(Level1Dep0); |
184 |
|
|
expect(instance.level2Dep0.level0Dep1).toBeInstanceOf(Level0Dep1); |
185 |
|
|
|
186 |
|
|
expect(instance.level2Dep1.level1Dep0).toBeInstanceOf(Level1Dep0); |
187 |
|
|
expect(instance.level2Dep1.level0Dep0).toBeInstanceOf(Level0Dep0); |
188 |
|
|
expect(instance.level2Dep1.level1Dep1).toBeInstanceOf(Level1Dep1); |
189 |
|
|
|
190 |
|
|
expect(instance.level2Dep1.level1Dep1.level0Dep0).toBeInstanceOf(Level0Dep0); |
191 |
|
|
expect(instance.level2Dep1.level1Dep1.level0Dep1).toBeInstanceOf(Level0Dep1); |
192 |
|
|
|
193 |
|
|
expect(instance.level2Dep1.level1Dep1.level0Dep0).toBe( |
194 |
|
|
instance.level2Dep0.level1Dep0.level0Dep0 |
195 |
|
|
); |
196 |
|
|
expect(instance.level2Dep1.level1Dep1.level0Dep1).toBe(instance.level2Dep0.level0Dep1); |
197 |
|
|
|
198 |
|
|
expect(instance.level2Dep0.level1Dep0.level0Dep0).toBe( |
199 |
|
|
instance.level2Dep1.level1Dep0.level0Dep0 |
200 |
|
|
); |
201 |
|
|
expect(instance.level2Dep0.level0Dep1).toBe(instance.level2Dep1.level1Dep1.level0Dep1); |
202 |
|
|
|
203 |
|
|
expect(instance.level2Dep1.level1Dep0.level0Dep0).toBe( |
204 |
|
|
instance.level2Dep0.level1Dep0.level0Dep0 |
205 |
|
|
); |
206 |
|
|
expect(instance.level2Dep1.level1Dep1.level0Dep1).toBe(instance.level2Dep0.level0Dep1); |
207 |
|
|
|
208 |
|
|
expect(instance.level2Dep1.level1Dep0.level0Dep0).toBe( |
209 |
|
|
instance.level2Dep0.level1Dep0.level0Dep0 |
210 |
|
|
); |
211 |
|
|
expect(instance.level2Dep1.level1Dep1.level0Dep1).toBe(instance.level2Dep0.level0Dep1); |
212 |
|
|
}); |
213 |
|
|
}); |
214 |
|
|
|
215 |
|
|
describe("container bindings with factories", () => { |
216 |
|
|
let container: Container; |
217 |
|
|
|
218 |
|
|
beforeEach(() => { |
219 |
|
|
container = Container.getInstance(); |
220 |
|
|
}); |
221 |
|
|
|
222 |
|
|
afterEach(() => { |
223 |
|
|
Container.destroyGlobalContainer(); |
224 |
|
|
}); |
225 |
|
|
|
226 |
|
|
it("should bind and resolve a class instance with a factory", async () => { |
227 |
|
|
class MyClass { |
228 |
|
|
public constructor(public value: number) {} |
229 |
|
|
} |
230 |
|
|
|
231 |
|
|
await container.bind(MyClass, { |
232 |
|
|
factory: () => new MyClass(crypto.getRandomValues(new Uint32Array(1))[0]) |
233 |
|
|
}); |
234 |
|
|
|
235 |
|
|
const instance = container.resolveByClass(MyClass); |
236 |
|
|
const instance2 = container.resolveByClass(MyClass); |
237 |
|
|
|
238 |
|
|
expect(instance).toBeInstanceOf(MyClass); |
239 |
|
|
expect(instance2).toBeInstanceOf(MyClass); |
240 |
|
|
expect(instance2).not.toBe(instance); |
241 |
|
|
expect(instance.value).not.toBe(instance2.value); |
242 |
|
|
}); |
243 |
|
|
|
244 |
|
|
it("should bind and resolve a singleton class instance with a factory", () => { |
245 |
|
|
class MyClass { |
246 |
|
|
public constructor(public value: number) {} |
247 |
|
|
} |
248 |
|
|
|
249 |
|
|
container.bind(MyClass, { |
250 |
|
|
factory: () => new MyClass(Math.ceil(Math.random() * 100)), |
251 |
|
|
singleton: true |
252 |
|
|
}); |
253 |
|
|
|
254 |
|
|
const instance = container.resolveByClass(MyClass); |
255 |
|
|
const instance2 = container.resolveByClass(MyClass); |
256 |
|
|
|
257 |
|
|
expect(instance).toBeInstanceOf(MyClass); |
258 |
|
|
expect(instance2).toBeInstanceOf(MyClass); |
259 |
|
|
expect(instance2).toBe(instance); |
260 |
|
|
expect(instance.value).toBe(instance2.value); |
261 |
|
|
}); |
262 |
|
|
}); |
263 |
|
|
|
264 |
|
|
describe("global containers", () => { |
265 |
|
|
it("should return the same container instance", () => { |
266 |
|
|
const container1 = Container.getInstance(); |
267 |
|
|
const container2 = Container.getInstance(); |
268 |
|
|
|
269 |
|
|
expect(container2).toBe(container1); |
270 |
|
|
}); |
271 |
|
|
|
272 |
|
|
it("should destroy the global container instance", () => { |
273 |
|
|
const container1 = Container.getInstance(); |
274 |
|
|
Container.destroyGlobalContainer(); |
275 |
|
|
const container2 = Container.getInstance(); |
276 |
|
|
|
277 |
|
|
expect(container2).not.toBe(container1); |
278 |
|
|
}); |
279 |
|
|
}); |
280 |
|
|
|
281 |
|
|
describe("container property injection", () => { |
282 |
|
|
let container: Container; |
283 |
|
|
|
284 |
|
|
beforeEach(() => { |
285 |
|
|
container = Container.getInstance(); |
286 |
|
|
}); |
287 |
|
|
|
288 |
|
|
afterEach(() => { |
289 |
|
|
Container.destroyGlobalContainer(); |
290 |
|
|
}); |
291 |
|
|
|
292 |
|
|
it("should inject a class instance with properties", () => { |
293 |
|
|
class MyClass { |
294 |
|
|
public value = 42; |
295 |
|
|
} |
296 |
|
|
|
297 |
|
|
class MyDependentClass { |
298 |
|
|
@Container.Inject() |
299 |
|
|
public myClass!: MyClass; |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
Reflect.defineMetadata("design:type", MyClass, MyDependentClass.prototype, "myClass"); |
303 |
|
|
|
304 |
|
|
container.bind(MyClass, { singleton: true }); |
305 |
|
|
container.bind(MyDependentClass, { singleton: true }); |
306 |
|
|
|
307 |
|
|
const instance = container.resolveByClass(MyDependentClass); |
308 |
|
|
|
309 |
|
|
expect(instance).toBeInstanceOf(MyDependentClass); |
310 |
|
|
expect(instance.myClass).toBeInstanceOf(MyClass); |
311 |
|
|
expect(instance.myClass.value).toBe(42); |
312 |
|
|
|
313 |
|
|
const instance2 = container.resolveByClass(MyDependentClass); |
314 |
|
|
|
315 |
|
|
expect(instance2).toBe(instance); |
316 |
|
|
expect(instance2.myClass).toBe(instance.myClass); |
317 |
|
|
expect(instance2.myClass.value).toBe(instance.myClass.value); |
318 |
|
|
|
319 |
|
|
instance.myClass.value = 100; |
320 |
|
|
|
321 |
|
|
expect(instance2.myClass.value).toBe(100); |
322 |
|
|
}); |
323 |
|
|
|
324 |
|
|
it("should inject a class instance with properties and singleton dependencies", () => { |
325 |
|
|
class MyClass { |
326 |
|
|
public value = 42; |
327 |
|
|
} |
328 |
|
|
|
329 |
|
|
class MyDependentClass { |
330 |
|
|
@Container.Inject() |
331 |
|
|
public myClass!: MyClass; |
332 |
|
|
} |
333 |
|
|
|
334 |
|
|
Reflect.defineMetadata("design:type", MyClass, MyDependentClass.prototype, "myClass"); |
335 |
|
|
|
336 |
|
|
container.bind(MyClass, { singleton: true }); |
337 |
|
|
container.bind(MyDependentClass, { singleton: true }); |
338 |
|
|
|
339 |
|
|
const instance = container.resolveByClass(MyDependentClass); |
340 |
|
|
|
341 |
|
|
expect(instance).toBeInstanceOf(MyDependentClass); |
342 |
|
|
expect(instance.myClass).toBeInstanceOf(MyClass); |
343 |
|
|
expect(instance.myClass.value).toBe(42); |
344 |
|
|
|
345 |
|
|
const instance2 = container.resolveByClass(MyDependentClass); |
346 |
|
|
|
347 |
|
|
expect(instance2).toBe(instance); |
348 |
|
|
expect(instance2.myClass).toBe(instance.myClass); |
349 |
|
|
expect(instance2.myClass.value).toBe(instance.myClass.value); |
350 |
|
|
|
351 |
|
|
instance.myClass.value = 100; |
352 |
|
|
|
353 |
|
|
expect(instance2.myClass.value).toBe(100); |
354 |
|
|
}); |
355 |
|
|
}); |