@navios/di 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/lib/testing/index.cjs +27 -14
- package/lib/testing/index.cjs.map +1 -1
- package/lib/testing/index.d.cts +6 -5
- package/lib/testing/index.d.cts.map +1 -1
- package/lib/testing/index.d.mts +6 -5
- package/lib/testing/index.d.mts.map +1 -1
- package/lib/testing/index.mjs +28 -15
- package/lib/testing/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/unit-test-container.spec.mts +128 -4
- package/src/testing/test-container.mts +23 -5
- package/src/testing/types.mts +8 -2
- package/src/testing/unit-test-container.mts +120 -28
package/src/testing/types.mts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BoundInjectionToken,
|
|
3
|
+
InjectionToken,
|
|
4
|
+
} from '../token/injection-token.mjs'
|
|
2
5
|
import type { Registry } from '../token/registry.mjs'
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -73,7 +76,10 @@ export interface BindingBuilder<T> {
|
|
|
73
76
|
* Provider configuration for UnitTestContainer.
|
|
74
77
|
*/
|
|
75
78
|
export interface ProviderConfig<T = any> {
|
|
76
|
-
token:
|
|
79
|
+
token:
|
|
80
|
+
| InjectionToken<T, any>
|
|
81
|
+
| BoundInjectionToken<T, any>
|
|
82
|
+
| (new (...args: any[]) => T)
|
|
77
83
|
useValue?: T
|
|
78
84
|
useClass?: new (...args: any[]) => T
|
|
79
85
|
useFactory?: () => T | Promise<T>
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
LifecycleRecord,
|
|
3
|
+
MethodCallRecord,
|
|
4
|
+
MockServiceStats,
|
|
5
|
+
ProviderConfig,
|
|
6
|
+
UnitTestContainerOptions,
|
|
7
|
+
} from './types.mjs'
|
|
2
8
|
|
|
3
9
|
import { Container } from '../container/container.mjs'
|
|
4
10
|
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
5
11
|
import { DIError } from '../errors/index.mjs'
|
|
6
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
BoundInjectionToken,
|
|
14
|
+
InjectionToken,
|
|
15
|
+
} from '../token/injection-token.mjs'
|
|
7
16
|
import { Registry } from '../token/registry.mjs'
|
|
8
17
|
import { getInjectableToken } from '../utils/get-injectable-token.mjs'
|
|
9
18
|
import { defaultInjectors } from '../utils/index.mjs'
|
|
10
19
|
|
|
11
|
-
type AnyToken =
|
|
20
|
+
type AnyToken =
|
|
21
|
+
| InjectionToken<any, any>
|
|
22
|
+
| BoundInjectionToken<any, any>
|
|
23
|
+
| (new (...args: any[]) => any)
|
|
12
24
|
|
|
13
25
|
/**
|
|
14
26
|
* Creates a tracking proxy that records method calls.
|
|
@@ -160,10 +172,17 @@ export class UnitTestContainer extends Container {
|
|
|
160
172
|
* Override get to wrap instances in tracking proxies.
|
|
161
173
|
*/
|
|
162
174
|
override async get(token: any, args?: unknown): Promise<any> {
|
|
175
|
+
// Check if token is a BoundInjectionToken and if it's registered
|
|
176
|
+
const isBoundToken = token instanceof BoundInjectionToken
|
|
177
|
+
const tokenId = isBoundToken ? token.id : undefined
|
|
163
178
|
const realToken = this.resolveToken(token)
|
|
164
179
|
|
|
165
|
-
// Check if this is a registered provider
|
|
166
|
-
|
|
180
|
+
// Check if this is a registered provider (check both bound token ID and real token ID)
|
|
181
|
+
const isRegistered =
|
|
182
|
+
(tokenId && this.registeredTokenIds.has(tokenId)) ||
|
|
183
|
+
this.registeredTokenIds.has(realToken.id)
|
|
184
|
+
|
|
185
|
+
if (!isRegistered) {
|
|
167
186
|
if (!this.allowUnregistered) {
|
|
168
187
|
throw DIError.factoryNotFound(
|
|
169
188
|
`${realToken.toString()} is not in the providers list. ` +
|
|
@@ -172,18 +191,20 @@ export class UnitTestContainer extends Container {
|
|
|
172
191
|
}
|
|
173
192
|
|
|
174
193
|
// Auto-mock unregistered dependency
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
const idToCheck = tokenId || realToken.id
|
|
195
|
+
if (!this.autoMockedTokenIds.has(idToCheck)) {
|
|
196
|
+
this.autoMockedTokenIds.add(idToCheck)
|
|
177
197
|
}
|
|
178
198
|
|
|
179
|
-
return createAutoMockProxy(
|
|
199
|
+
return createAutoMockProxy(idToCheck)
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
const instance = await super.get(token, args)
|
|
183
203
|
|
|
184
204
|
// Wrap in tracking proxy if it's an object
|
|
205
|
+
const trackingId = tokenId || realToken.id
|
|
185
206
|
if (instance && typeof instance === 'object') {
|
|
186
|
-
return createTrackingProxy(instance,
|
|
207
|
+
return createTrackingProxy(instance, trackingId, this.methodCalls)
|
|
187
208
|
}
|
|
188
209
|
|
|
189
210
|
return instance
|
|
@@ -214,7 +235,9 @@ export class UnitTestContainer extends Container {
|
|
|
214
235
|
const found = names.some((name) => name.includes(realToken.id))
|
|
215
236
|
|
|
216
237
|
if (!found) {
|
|
217
|
-
throw new Error(
|
|
238
|
+
throw new Error(
|
|
239
|
+
`Expected ${realToken.toString()} to be resolved, but it was not`,
|
|
240
|
+
)
|
|
218
241
|
}
|
|
219
242
|
}
|
|
220
243
|
|
|
@@ -228,7 +251,9 @@ export class UnitTestContainer extends Container {
|
|
|
228
251
|
const found = names.some((name) => name.includes(realToken.id))
|
|
229
252
|
|
|
230
253
|
if (found) {
|
|
231
|
-
throw new Error(
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Expected ${realToken.toString()} to NOT be resolved, but it was`,
|
|
256
|
+
)
|
|
232
257
|
}
|
|
233
258
|
}
|
|
234
259
|
|
|
@@ -266,7 +291,11 @@ export class UnitTestContainer extends Container {
|
|
|
266
291
|
/**
|
|
267
292
|
* Records a lifecycle event for tracking.
|
|
268
293
|
*/
|
|
269
|
-
recordLifecycleEvent(
|
|
294
|
+
recordLifecycleEvent(
|
|
295
|
+
token: AnyToken,
|
|
296
|
+
event: 'created' | 'initialized' | 'destroyed',
|
|
297
|
+
instanceName: string,
|
|
298
|
+
): void {
|
|
270
299
|
const realToken = this.resolveToken(token)
|
|
271
300
|
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
272
301
|
events.push({
|
|
@@ -291,7 +320,9 @@ export class UnitTestContainer extends Container {
|
|
|
291
320
|
const initialized = events.some((e) => e.event === 'initialized')
|
|
292
321
|
|
|
293
322
|
if (!initialized) {
|
|
294
|
-
throw new Error(
|
|
323
|
+
throw new Error(
|
|
324
|
+
`Expected ${realToken.toString()} to be initialized, but onServiceInit was not called`,
|
|
325
|
+
)
|
|
295
326
|
}
|
|
296
327
|
}
|
|
297
328
|
|
|
@@ -304,7 +335,9 @@ export class UnitTestContainer extends Container {
|
|
|
304
335
|
const destroyed = events.some((e) => e.event === 'destroyed')
|
|
305
336
|
|
|
306
337
|
if (!destroyed) {
|
|
307
|
-
throw new Error(
|
|
338
|
+
throw new Error(
|
|
339
|
+
`Expected ${realToken.toString()} to be destroyed, but onServiceDestroy was not called`,
|
|
340
|
+
)
|
|
308
341
|
}
|
|
309
342
|
}
|
|
310
343
|
|
|
@@ -317,7 +350,9 @@ export class UnitTestContainer extends Container {
|
|
|
317
350
|
const destroyed = events.some((e) => e.event === 'destroyed')
|
|
318
351
|
|
|
319
352
|
if (destroyed) {
|
|
320
|
-
throw new Error(
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Expected ${realToken.toString()} to NOT be destroyed, but onServiceDestroy was called`,
|
|
355
|
+
)
|
|
321
356
|
}
|
|
322
357
|
}
|
|
323
358
|
|
|
@@ -334,7 +369,9 @@ export class UnitTestContainer extends Container {
|
|
|
334
369
|
const found = calls.some((c) => c.method === method)
|
|
335
370
|
|
|
336
371
|
if (!found) {
|
|
337
|
-
throw new Error(
|
|
372
|
+
throw new Error(
|
|
373
|
+
`Expected ${realToken.toString()}.${method}() to be called, but it was not`,
|
|
374
|
+
)
|
|
338
375
|
}
|
|
339
376
|
}
|
|
340
377
|
|
|
@@ -347,14 +384,20 @@ export class UnitTestContainer extends Container {
|
|
|
347
384
|
const found = calls.some((c) => c.method === method)
|
|
348
385
|
|
|
349
386
|
if (found) {
|
|
350
|
-
throw new Error(
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Expected ${realToken.toString()}.${method}() to NOT be called, but it was`,
|
|
389
|
+
)
|
|
351
390
|
}
|
|
352
391
|
}
|
|
353
392
|
|
|
354
393
|
/**
|
|
355
394
|
* Asserts that a method was called with specific arguments.
|
|
356
395
|
*/
|
|
357
|
-
expectCalledWith(
|
|
396
|
+
expectCalledWith(
|
|
397
|
+
token: AnyToken,
|
|
398
|
+
method: string,
|
|
399
|
+
expectedArgs: unknown[],
|
|
400
|
+
): void {
|
|
358
401
|
const realToken = this.resolveToken(token)
|
|
359
402
|
const calls = this.methodCalls.get(realToken.id) || []
|
|
360
403
|
const found = calls.some(
|
|
@@ -363,7 +406,9 @@ export class UnitTestContainer extends Container {
|
|
|
363
406
|
|
|
364
407
|
if (!found) {
|
|
365
408
|
const methodCalls = calls.filter((c) => c.method === method)
|
|
366
|
-
const actualArgs = methodCalls
|
|
409
|
+
const actualArgs = methodCalls
|
|
410
|
+
.map((c) => JSON.stringify(c.args))
|
|
411
|
+
.join(', ')
|
|
367
412
|
throw new Error(
|
|
368
413
|
`Expected ${realToken.toString()}.${method}() to be called with ${JSON.stringify(expectedArgs)}. ` +
|
|
369
414
|
`Actual calls: [${actualArgs}]`,
|
|
@@ -440,12 +485,21 @@ export class UnitTestContainer extends Container {
|
|
|
440
485
|
return InjectionToken.create(token)
|
|
441
486
|
}
|
|
442
487
|
}
|
|
488
|
+
if (token instanceof BoundInjectionToken) {
|
|
489
|
+
return token.token
|
|
490
|
+
}
|
|
443
491
|
return token
|
|
444
492
|
}
|
|
445
493
|
|
|
446
494
|
private registerProvider<T>(provider: ProviderConfig<T>): void {
|
|
447
|
-
const
|
|
495
|
+
const providerToken = provider.token as AnyToken
|
|
496
|
+
const realToken = this.resolveToken(providerToken)
|
|
497
|
+
|
|
498
|
+
// Track both the real token ID and the bound token ID if it's a bound token
|
|
448
499
|
this.registeredTokenIds.add(realToken.id)
|
|
500
|
+
if (providerToken instanceof BoundInjectionToken) {
|
|
501
|
+
this.registeredTokenIds.add(providerToken.id)
|
|
502
|
+
}
|
|
449
503
|
|
|
450
504
|
if (provider.useValue !== undefined) {
|
|
451
505
|
this.registerValueBinding(realToken, provider.useValue)
|
|
@@ -456,17 +510,37 @@ export class UnitTestContainer extends Container {
|
|
|
456
510
|
} else {
|
|
457
511
|
// Just the token - register as itself
|
|
458
512
|
if (typeof provider.token === 'function') {
|
|
459
|
-
this.testRegistry.set(
|
|
513
|
+
this.testRegistry.set(
|
|
514
|
+
realToken,
|
|
515
|
+
InjectableScope.Singleton,
|
|
516
|
+
provider.token,
|
|
517
|
+
InjectableType.Class,
|
|
518
|
+
1000, // Higher priority for test overrides
|
|
519
|
+
)
|
|
520
|
+
} else if (providerToken instanceof BoundInjectionToken) {
|
|
521
|
+
// If it's a bound token without override, register the bound value
|
|
522
|
+
this.registerValueBinding(realToken, providerToken.value)
|
|
460
523
|
}
|
|
461
524
|
}
|
|
462
525
|
}
|
|
463
526
|
|
|
464
|
-
private registerValueBinding<T>(
|
|
527
|
+
private registerValueBinding<T>(
|
|
528
|
+
token: InjectionToken<T, any>,
|
|
529
|
+
value: T,
|
|
530
|
+
): void {
|
|
465
531
|
const ValueHolder = class {
|
|
466
|
-
|
|
532
|
+
create(): T {
|
|
533
|
+
return value
|
|
534
|
+
}
|
|
467
535
|
}
|
|
468
536
|
|
|
469
|
-
this.testRegistry.set(
|
|
537
|
+
this.testRegistry.set(
|
|
538
|
+
token,
|
|
539
|
+
InjectableScope.Singleton,
|
|
540
|
+
ValueHolder,
|
|
541
|
+
InjectableType.Factory,
|
|
542
|
+
1000, // Higher priority for test overrides
|
|
543
|
+
)
|
|
470
544
|
|
|
471
545
|
const nameResolver = this.getNameResolver()
|
|
472
546
|
const instanceName = nameResolver.generateInstanceName(
|
|
@@ -479,11 +553,23 @@ export class UnitTestContainer extends Container {
|
|
|
479
553
|
this.recordLifecycleEvent(token, 'created', instanceName)
|
|
480
554
|
}
|
|
481
555
|
|
|
482
|
-
private registerClassBinding<T>(
|
|
483
|
-
|
|
556
|
+
private registerClassBinding<T>(
|
|
557
|
+
token: InjectionToken<T, any>,
|
|
558
|
+
cls: new (...args: any[]) => T,
|
|
559
|
+
): void {
|
|
560
|
+
this.testRegistry.set(
|
|
561
|
+
token,
|
|
562
|
+
InjectableScope.Singleton,
|
|
563
|
+
cls,
|
|
564
|
+
InjectableType.Class,
|
|
565
|
+
1000, // Higher priority for test overrides
|
|
566
|
+
)
|
|
484
567
|
}
|
|
485
568
|
|
|
486
|
-
private registerFactoryBinding<T>(
|
|
569
|
+
private registerFactoryBinding<T>(
|
|
570
|
+
token: InjectionToken<T, any>,
|
|
571
|
+
factory: () => T | Promise<T>,
|
|
572
|
+
): void {
|
|
487
573
|
const FactoryWrapper = class {
|
|
488
574
|
static factory = factory
|
|
489
575
|
async create(): Promise<T> {
|
|
@@ -491,7 +577,13 @@ export class UnitTestContainer extends Container {
|
|
|
491
577
|
}
|
|
492
578
|
}
|
|
493
579
|
|
|
494
|
-
this.testRegistry.set(
|
|
580
|
+
this.testRegistry.set(
|
|
581
|
+
token,
|
|
582
|
+
InjectableScope.Singleton,
|
|
583
|
+
FactoryWrapper,
|
|
584
|
+
InjectableType.Factory,
|
|
585
|
+
1000, // Higher priority for test overrides
|
|
586
|
+
)
|
|
495
587
|
}
|
|
496
588
|
|
|
497
589
|
private argsMatch(actual: unknown[], expected: unknown[]): boolean {
|