@codefast/di-testing 0.0.0
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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/discovery/dependency-scanner.d.ts +29 -0
- package/dist/discovery/dependency-scanner.d.ts.map +1 -0
- package/dist/discovery/dependency-scanner.js +51 -0
- package/dist/discovery/dependency-scanner.js.map +1 -0
- package/dist/discovery/mock-binder.d.ts +71 -0
- package/dist/discovery/mock-binder.d.ts.map +1 -0
- package/dist/discovery/mock-binder.js +206 -0
- package/dist/discovery/mock-binder.js.map +1 -0
- package/dist/errors/errors.d.ts +62 -0
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/errors/errors.js +83 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mocking/auto-mock.d.ts +47 -0
- package/dist/mocking/auto-mock.d.ts.map +1 -0
- package/dist/mocking/auto-mock.js +180 -0
- package/dist/mocking/auto-mock.js.map +1 -0
- package/dist/mocking/mock-factory.d.ts +25 -0
- package/dist/mocking/mock-factory.d.ts.map +1 -0
- package/dist/mocking/mock-factory.js +7 -0
- package/dist/mocking/mock-factory.js.map +1 -0
- package/dist/mocking/spy.d.ts +39 -0
- package/dist/mocking/spy.d.ts.map +1 -0
- package/dist/mocking/spy.js +43 -0
- package/dist/mocking/spy.js.map +1 -0
- package/dist/test-bed/bed-builder.d.ts +83 -0
- package/dist/test-bed/bed-builder.d.ts.map +1 -0
- package/dist/test-bed/bed-builder.js +95 -0
- package/dist/test-bed/bed-builder.js.map +1 -0
- package/dist/test-bed/sociable-builder.d.ts +41 -0
- package/dist/test-bed/sociable-builder.d.ts.map +1 -0
- package/dist/test-bed/sociable-builder.js +78 -0
- package/dist/test-bed/sociable-builder.js.map +1 -0
- package/dist/test-bed/solitary-builder.d.ts +34 -0
- package/dist/test-bed/solitary-builder.d.ts.map +1 -0
- package/dist/test-bed/solitary-builder.js +29 -0
- package/dist/test-bed/solitary-builder.js.map +1 -0
- package/dist/test-bed/test-bed.d.ts +35 -0
- package/dist/test-bed/test-bed.d.ts.map +1 -0
- package/dist/test-bed/test-bed.js +18 -0
- package/dist/test-bed/test-bed.js.map +1 -0
- package/dist/test-bed/unit-test-bed.d.ts +65 -0
- package/dist/test-bed/unit-test-bed.d.ts.map +1 -0
- package/dist/test-bed/unit-test-bed.js +70 -0
- package/dist/test-bed/unit-test-bed.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +156 -0
- package/src/discovery/dependency-scanner.ts +74 -0
- package/src/discovery/mock-binder.ts +299 -0
- package/src/errors/errors.ts +103 -0
- package/src/index.ts +26 -0
- package/src/mocking/auto-mock.ts +235 -0
- package/src/mocking/mock-factory.ts +29 -0
- package/src/mocking/spy.ts +84 -0
- package/src/test-bed/bed-builder.ts +164 -0
- package/src/test-bed/sociable-builder.ts +144 -0
- package/src/test-bed/solitary-builder.ts +66 -0
- package/src/test-bed/test-bed.ts +60 -0
- package/src/test-bed/unit-test-bed.ts +151 -0
- package/src/types.ts +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 CodeFast Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# @codefast/di-testing
|
|
2
|
+
|
|
3
|
+
Solitary and sociable auto-mocking test beds for
|
|
4
|
+
[`@codefast/di`](https://github.com/codefastlabs/codefast/tree/main/packages/di) — write an isolated unit test for an
|
|
5
|
+
`@injectable` class in two lines, with every collaborator mocked for you, or keep chosen collaborators real.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@codefast/di-testing)
|
|
8
|
+
[](https://github.com/codefastlabs/codefast/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
- **Auto-mocking.** `TestBed.solitary(Class)` reads the class's declared dependencies through di's own `MetadataReader`
|
|
11
|
+
and builds a mock for each — no per-collaborator `bind(...).toConstantValue(...)`.
|
|
12
|
+
- **Real instance, real wiring.** The unit is constructed through a container, so `@postConstruct`, accessor injection,
|
|
13
|
+
and `@preDestroy` run exactly as in production.
|
|
14
|
+
- **Zero test-framework dependency.** The default mock is a small built-in spy. Pass `() => vi.fn()` (or `jest.fn`,
|
|
15
|
+
`() => sinon.stub()`) to build the mocks from that backend and use its matchers instead.
|
|
16
|
+
- **Backend-typed lookups.** The factory's return type flows through the whole bed: with `() => vi.fn()`,
|
|
17
|
+
`mocks.get(EmailToken).send` carries Vitest's own mock surface (`mockReturnValueOnce`, `mockClear`, …) — no adapter
|
|
18
|
+
package, no module augmentation, no postinstall scripts.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- **Node.js 24 or later**, matching `@codefast/di`.
|
|
23
|
+
- A peer install of `@codefast/di` with `@injectable` / `inject` classes to test.
|
|
24
|
+
- Types for `AsyncDisposable` (`await using`): `@types/node` provides them; without it, add `"ESNext.Disposable"` to
|
|
25
|
+
your tsconfig `lib`.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add -D @codefast/di-testing
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { inject, injectable, token } from "@codefast/di";
|
|
37
|
+
import { TestBed } from "@codefast/di-testing";
|
|
38
|
+
import { expect, it, vi } from "vitest";
|
|
39
|
+
|
|
40
|
+
const UserServiceToken = token<UserService>("UserService");
|
|
41
|
+
const PaymentGatewayToken = token<PaymentGateway>("PaymentGateway");
|
|
42
|
+
const EmailServiceToken = token<EmailService>("EmailService");
|
|
43
|
+
|
|
44
|
+
@injectable([inject(UserServiceToken), inject(PaymentGatewayToken), inject(EmailServiceToken)])
|
|
45
|
+
class OrderProcessor {
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly users: UserService,
|
|
48
|
+
private readonly payments: PaymentGateway,
|
|
49
|
+
private readonly email: EmailService,
|
|
50
|
+
) {}
|
|
51
|
+
|
|
52
|
+
placeOrder(userId: string, amount: number): string {
|
|
53
|
+
const user = this.users.findUser(userId);
|
|
54
|
+
this.payments.charge(userId, amount);
|
|
55
|
+
this.email.send(user.email, `Order confirmed — ${amount}`);
|
|
56
|
+
return `ord-${userId}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
it("charges then emails a confirmation", () => {
|
|
61
|
+
const { unit, mocks } = TestBed.solitary(OrderProcessor, { mockFactory: () => vi.fn() })
|
|
62
|
+
.mock(UserServiceToken)
|
|
63
|
+
.stub((fn) => ({ findUser: fn().mockReturnValue({ id: "u1", email: "alice@example.com" }) }))
|
|
64
|
+
.compile();
|
|
65
|
+
|
|
66
|
+
unit.placeOrder("u1", 42);
|
|
67
|
+
|
|
68
|
+
expect(mocks.get(PaymentGatewayToken).charge).toHaveBeenCalledWith("u1", 42);
|
|
69
|
+
expect(mocks.get(EmailServiceToken).send).toHaveBeenCalledWith("alice@example.com", "Order confirmed — 42");
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The zero-dependency default reads the same, minus the `mockFactory` — assert against the built-in spy's `.mock.calls`
|
|
74
|
+
and stub with `.mockReturnValue()`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { TestBed } from "@codefast/di-testing";
|
|
78
|
+
import assert from "node:assert/strict";
|
|
79
|
+
|
|
80
|
+
const { unit, mocks } = TestBed.solitary(OrderProcessor).compile();
|
|
81
|
+
unit.placeOrder("u1", 42);
|
|
82
|
+
assert.deepEqual(mocks.get(PaymentGatewayToken).charge.mock.calls[0], ["u1", 42]);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API
|
|
86
|
+
|
|
87
|
+
### `TestBed.solitary(target, options?)`
|
|
88
|
+
|
|
89
|
+
Begins a solitary test bed for `target`, auto-mocking every dependency it declares. `options`:
|
|
90
|
+
|
|
91
|
+
- `mockFactory?: () => spy` — the spy backend for each auto-mock. Defaults to the built-in zero-dependency spy.
|
|
92
|
+
- `metadataReader?: MetadataReader` — the reader dependencies are discovered through. Defaults to di's reader.
|
|
93
|
+
|
|
94
|
+
### Builder
|
|
95
|
+
|
|
96
|
+
- `.mock(token).stub((fn) => stub)` — bind a partial stub built from the active spy factory; unlisted members stay
|
|
97
|
+
auto-mocked. `fn()` is typed as whatever the backend produces, so `fn().mockReturnValue(...)` (jest-shaped) or
|
|
98
|
+
`fn().returns(...)` (Sinon) both type-check against the factory you chose.
|
|
99
|
+
- `.mock(token).using(value)` — bind a fixed value. The value is **sealed**: it has no mock surface, so `mocks.get`
|
|
100
|
+
refuses it rather than hand it back mistyped — the test already holds the reference it passed in.
|
|
101
|
+
- `.mock(token).absent()` — leave the dependency unbound: an `optional()` slot resolves `undefined`, an `injectAll()`
|
|
102
|
+
slot `[]`. Anything else is an `OverrideMismatchError`.
|
|
103
|
+
- `.mock(token).usingAll([a, b])` — supply the elements of an unconstrained `injectAll()` slot, in order. Sealed like
|
|
104
|
+
`.using`.
|
|
105
|
+
- `.mock(token, { name })` / `.mock(token, { tag })` — target one slot of a token that is injected several ways; the
|
|
106
|
+
slotless form covers every slot without a more specific override.
|
|
107
|
+
- `.compile()` — instantiate the unit synchronously (the primary path).
|
|
108
|
+
- `.compileAsync()` — for a unit whose `@postConstruct` is asynchronous.
|
|
109
|
+
|
|
110
|
+
### `TestBed.sociable(target, options?)`
|
|
111
|
+
|
|
112
|
+
A sociable bed keeps chosen collaborators real while everything else stays mocked — a unit test over a small real
|
|
113
|
+
subtree, not an integration test. It returns only `.expose()`, steering the first call:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const bed = TestBed.sociable(CheckoutService, { mockFactory: () => vi.fn() })
|
|
117
|
+
.expose(PricingService) // real instance, wired through the container
|
|
118
|
+
.mock(TaxPolicyToken)
|
|
119
|
+
.stub((fn) => ({ rateFor: fn().mockReturnValue(0.1) }))
|
|
120
|
+
.compile();
|
|
121
|
+
|
|
122
|
+
bed.unit.checkout(100, "USD"); // real PricingService math over the mocked tax boundary
|
|
123
|
+
bed.exposed(PricingService); // the real instance the unit was built with
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- **Exposure follows class identity.** A class-keyed dependency — of the unit or of another exposed class — stays real
|
|
127
|
+
when exposed, and its own dependencies follow the same rules recursively.
|
|
128
|
+
- **Tokens are the boundary.** A `Token`-keyed dependency is always mocked, in both modes: tokens mark where the logic
|
|
129
|
+
under test meets the outside world.
|
|
130
|
+
- Exposed collaborators are real singletons resolved through the container, so their `@postConstruct` runs at compile
|
|
131
|
+
and `@preDestroy` on dispose. `bed.exposed(Class)` retrieves them; `mocks.get(Class)` refuses them
|
|
132
|
+
(`SealedDependencyError`) because they carry no mock surface.
|
|
133
|
+
- Exposing a class the unit never reaches, or exposing and mocking the same class, is an error at compile.
|
|
134
|
+
|
|
135
|
+
### Behavior notes
|
|
136
|
+
|
|
137
|
+
- An `optional()` dependency is auto-mocked like any other — it resolves to the mock, not `undefined` as an unbound
|
|
138
|
+
optional would in production. Use `.mock(token).absent()` to exercise the absent branch.
|
|
139
|
+
- An `injectAll()` dependency receives a one-element array holding the token's mock; use `.mock(token).usingAll([...])`
|
|
140
|
+
to supply several elements. When the same token also has a named or tagged slot, that slot's binding is collected by
|
|
141
|
+
the unconstrained `injectAll` too — di's `resolveAll` takes every binding of the token.
|
|
142
|
+
- Named or tagged parameters of one token share the token's mock unless a slot-targeted `.mock(token, { name })` gives
|
|
143
|
+
that slot its own; either way `mocks.get(token, { name })` addresses the slot directly.
|
|
144
|
+
|
|
145
|
+
### Result — `UnitTestBed`
|
|
146
|
+
|
|
147
|
+
- `unit` — the real class under test.
|
|
148
|
+
- `mocks.get(token, options?)` — the `Mocked<T>` bound for a dependency (or one slot of it). Only auto-mocks and `.stub`
|
|
149
|
+
stubs come back; sealed values throw `SealedDependencyError`.
|
|
150
|
+
- `resetMocks()` — clear the call history and configured behaviour of every auto-mock the bed created.
|
|
151
|
+
- `dispose()` — run the unit's `@preDestroy` hooks and dispose the container. Also runs via `await using`.
|
|
152
|
+
|
|
153
|
+
## Errors
|
|
154
|
+
|
|
155
|
+
- `NotInjectableError` — the class under test takes constructor parameters but is not `@injectable`.
|
|
156
|
+
- `UndeclaredDependencyError` — a `.mock(...)` or `mocks.get(...)` named a token or slot the class does not use.
|
|
157
|
+
- `SealedDependencyError` — `mocks.get(...)` asked for a `.using()`/`.absent()`/`.all()` value.
|
|
158
|
+
- `OverrideMismatchError` — `.absent()` on a required dependency, or `.all()` on a non-`injectAll` slot.
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Reads the dependencies a class declares, the input every auto-mock is built from. */
|
|
2
|
+
import type { Constructor, DependencySlot, MetadataReader } from "@codefast/di";
|
|
3
|
+
/**
|
|
4
|
+
* Reads every dependency a class declares — constructor parameters first, then accessor injections.
|
|
5
|
+
*
|
|
6
|
+
* @remarks Mirrors di's own fallback: a class with no constructor metadata is fine as long as its
|
|
7
|
+
* constructor takes no parameters, which keeps zero-arg and accessor-only classes testable without
|
|
8
|
+
* `@injectable`. A parameterful constructor with no metadata is a {@link NotInjectableError}.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scanDependencies(target: Constructor, reader: MetadataReader): ReadonlyArray<DependencySlot>;
|
|
11
|
+
/**
|
|
12
|
+
* What a sociable scan finds: the slots to mock, the exposed classes the unit actually reaches, and
|
|
13
|
+
* the constrained slots those classes are injected through.
|
|
14
|
+
*/
|
|
15
|
+
export interface SociableScan {
|
|
16
|
+
readonly mockSlots: ReadonlyArray<DependencySlot>;
|
|
17
|
+
readonly realClasses: ReadonlySet<Constructor>;
|
|
18
|
+
/** The name/tag-constrained slots each exposed class is requested through, for alias replay. */
|
|
19
|
+
readonly realSlots: ReadonlyArray<DependencySlot>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Walks the unit's dependency graph across the exposed set: an exposed class dependency stays real
|
|
23
|
+
* and is scanned in turn, everything else becomes a mock slot.
|
|
24
|
+
*
|
|
25
|
+
* @remarks Class identity is what exposure follows — a `Token`-keyed dependency is always mocked,
|
|
26
|
+
* treating tokens as the declared boundary between the logic under test and the outside world.
|
|
27
|
+
*/
|
|
28
|
+
export declare function scanSociableDependencies(target: Constructor, exposed: ReadonlySet<Constructor>, reader: MetadataReader): SociableScan;
|
|
29
|
+
//# sourceMappingURL=dependency-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-scanner.d.ts","sourceRoot":"","sources":["../../src/discovery/dependency-scanner.ts"],"names":[],"mappings":"AAAA,wFAAwF;AAExF,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAIhF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,GAAG,aAAa,CAAC,cAAc,CAAC,CAS3G;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAClD,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAC/C,gGAAgG;IAChG,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACnD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,WAAW,CAAC,WAAW,CAAC,EACjC,MAAM,EAAE,cAAc,GACrB,YAAY,CA2Bd"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Reads the dependencies a class declares, the input every auto-mock is built from. */
|
|
2
|
+
import { NotInjectableError } from "#/errors/errors";
|
|
3
|
+
/**
|
|
4
|
+
* Reads every dependency a class declares — constructor parameters first, then accessor injections.
|
|
5
|
+
*
|
|
6
|
+
* @remarks Mirrors di's own fallback: a class with no constructor metadata is fine as long as its
|
|
7
|
+
* constructor takes no parameters, which keeps zero-arg and accessor-only classes testable without
|
|
8
|
+
* `@injectable`. A parameterful constructor with no metadata is a {@link NotInjectableError}.
|
|
9
|
+
*/
|
|
10
|
+
export function scanDependencies(target, reader) {
|
|
11
|
+
const constructorMetadata = reader.getConstructorMetadata(target);
|
|
12
|
+
if (constructorMetadata === undefined && target.length > 0) {
|
|
13
|
+
throw new NotInjectableError(target.name);
|
|
14
|
+
}
|
|
15
|
+
const accessors = reader.getAccessorMetadata?.(target) ?? [];
|
|
16
|
+
return [...(constructorMetadata?.params ?? []), ...accessors.map((accessor) => accessor.descriptor)];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Walks the unit's dependency graph across the exposed set: an exposed class dependency stays real
|
|
20
|
+
* and is scanned in turn, everything else becomes a mock slot.
|
|
21
|
+
*
|
|
22
|
+
* @remarks Class identity is what exposure follows — a `Token`-keyed dependency is always mocked,
|
|
23
|
+
* treating tokens as the declared boundary between the logic under test and the outside world.
|
|
24
|
+
*/
|
|
25
|
+
export function scanSociableDependencies(target, exposed, reader) {
|
|
26
|
+
const mockSlots = [];
|
|
27
|
+
const realClasses = new Set();
|
|
28
|
+
const realSlots = [];
|
|
29
|
+
const visited = new Set([target]);
|
|
30
|
+
const queue = [target];
|
|
31
|
+
while (queue.length > 0) {
|
|
32
|
+
const current = queue.shift();
|
|
33
|
+
for (const slot of scanDependencies(current, reader)) {
|
|
34
|
+
const token = slot.token;
|
|
35
|
+
if (typeof token === "function" && exposed.has(token)) {
|
|
36
|
+
if (slot.name !== undefined || (slot.tags?.length ?? 0) > 0) {
|
|
37
|
+
realSlots.push(slot);
|
|
38
|
+
}
|
|
39
|
+
if (!visited.has(token)) {
|
|
40
|
+
visited.add(token);
|
|
41
|
+
realClasses.add(token);
|
|
42
|
+
queue.push(token);
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
mockSlots.push(slot);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { mockSlots, realClasses, realSlots };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=dependency-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-scanner.js","sourceRoot":"","sources":["../../src/discovery/dependency-scanner.ts"],"names":[],"mappings":"AAAA,wFAAwF;AAIxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAmB,EAAE,MAAsB;IAC1E,MAAM,mBAAmB,GAAG,MAAM,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAClE,IAAI,mBAAmB,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAE7D,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;AACvG,CAAC;AAaD;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAmB,EACnB,OAAiC,EACjC,MAAsB;IAEtB,MAAM,SAAS,GAA0B,EAAE,CAAC;IAC5C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAe,CAAC;IAC3C,MAAM,SAAS,GAA0B,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAuB,CAAC,MAAM,CAAC,CAAC;IAE3C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YACzB,IAAI,OAAO,KAAK,KAAK,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5D,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvB,CAAC;gBACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACnB,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACvB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACpB,CAAC;gBACD,SAAS;YACX,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Binds a unit's discovered dependencies onto a container as mocks — the sole container coupling. */
|
|
2
|
+
import type { BindingTag, Constructor, Container, DependencyKey, DependencySlot, InjectOptions } from "@codefast/di";
|
|
3
|
+
import type { MockFactory } from "#/mocking/mock-factory";
|
|
4
|
+
/**
|
|
5
|
+
* How one dependency is supplied instead of a plain auto-mock.
|
|
6
|
+
*
|
|
7
|
+
* @remarks A stub's `setup` runs once per compile, so two beds built from one builder never share
|
|
8
|
+
* seeded spies.
|
|
9
|
+
*/
|
|
10
|
+
export type MockOverride = {
|
|
11
|
+
readonly kind: "stub";
|
|
12
|
+
readonly setup: (mock: MockFactory) => unknown;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: "value";
|
|
15
|
+
readonly value: unknown;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "absent";
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "all";
|
|
20
|
+
readonly values: ReadonlyArray<unknown>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Normalized slot criteria an override or a `mocks.get` lookup addresses one binding slot with.
|
|
24
|
+
*/
|
|
25
|
+
export interface SlotCriteria {
|
|
26
|
+
readonly name: string | undefined;
|
|
27
|
+
readonly tags: ReadonlyArray<BindingTag>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* One override registration: how a dependency is supplied, and which slot it targets (none = every
|
|
31
|
+
* slot of the token that has no more specific override).
|
|
32
|
+
*/
|
|
33
|
+
export interface SlottedOverride {
|
|
34
|
+
readonly criteria: SlotCriteria | undefined;
|
|
35
|
+
readonly override: MockOverride;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* What one bound entry was supplied with — only `auto` and `stub` entries carry a mock surface.
|
|
39
|
+
*/
|
|
40
|
+
export type BoundMockKind = "auto" | "stub" | "value" | "absent" | "all" | "exposed";
|
|
41
|
+
/**
|
|
42
|
+
* One bound value `mocks` can look up: its slot criteria, the value, and how it was supplied.
|
|
43
|
+
*/
|
|
44
|
+
export interface BoundMock {
|
|
45
|
+
readonly criteria: SlotCriteria | undefined;
|
|
46
|
+
readonly value: unknown;
|
|
47
|
+
readonly kind: BoundMockKind;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Folds an `InjectOptions` into slot criteria, or `undefined` when it names no criterion.
|
|
51
|
+
*
|
|
52
|
+
* @remarks Tags are copied and deduplicated by identity — di interns each `(key, value)` pair, so a
|
|
53
|
+
* repeated pair is the same object.
|
|
54
|
+
*/
|
|
55
|
+
export declare function normalizeCriteria(options: InjectOptions | undefined): SlotCriteria | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Returns whether two criteria address the same slot — equal name and the same tag set.
|
|
58
|
+
*/
|
|
59
|
+
export declare function criteriaEquals(left: SlotCriteria, right: SlotCriteria): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Binds a mock for every discovered dependency and returns the entries `mocks` reads.
|
|
62
|
+
*
|
|
63
|
+
* @remarks One shared mock per token for slots without a specific override — duplicate tokens across
|
|
64
|
+
* parameters share it — while an override carrying slot criteria supplies just its own slot; every
|
|
65
|
+
* constrained slot is also recorded under its own criteria so `mocks.get(token, options)` resolves.
|
|
66
|
+
* An override that matches nothing is an {@link UndeclaredDependencyError}; `.absent()` on a
|
|
67
|
+
* required slot or `.usingAll()` with no unconstrained `injectAll()` slot is an
|
|
68
|
+
* {@link OverrideMismatchError}.
|
|
69
|
+
*/
|
|
70
|
+
export declare function bindMocks(container: Container, dependencies: ReadonlyArray<DependencySlot>, overrides: ReadonlyMap<DependencyKey, ReadonlyArray<SlottedOverride>>, mockFactory: MockFactory, exposed?: ReadonlySet<Constructor>): ReadonlyMap<DependencyKey, ReadonlyArray<BoundMock>>;
|
|
71
|
+
//# sourceMappingURL=mock-binder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-binder.d.ts","sourceRoot":"","sources":["../../src/discovery/mock-binder.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAEtG,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAMrH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAA;CAAE,GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CAC1C;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;AAErF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;CAC9B;AAKD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAU9F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAE/E;AAgDD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,aAAa,CAAC,cAAc,CAAC,EAC3C,SAAS,EAAE,WAAW,CAAC,aAAa,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC,EACrE,WAAW,EAAE,WAAW,EACxB,OAAO,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,GACjC,WAAW,CAAC,aAAa,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CA4ItD"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/** Binds a unit's discovered dependencies onto a container as mocks — the sole container coupling. */
|
|
2
|
+
import { tokenName } from "@codefast/di";
|
|
3
|
+
import { OverrideMismatchError, UndeclaredDependencyError } from "#/errors/errors";
|
|
4
|
+
import { createAutoMock } from "#/mocking/auto-mock";
|
|
5
|
+
/** The name prefix `usingAll` elements are bound under, so identical constants keep distinct slots. */
|
|
6
|
+
const ALL_ELEMENT_SLOT_PREFIX = "di-testing:all:";
|
|
7
|
+
/**
|
|
8
|
+
* Folds an `InjectOptions` into slot criteria, or `undefined` when it names no criterion.
|
|
9
|
+
*
|
|
10
|
+
* @remarks Tags are copied and deduplicated by identity — di interns each `(key, value)` pair, so a
|
|
11
|
+
* repeated pair is the same object.
|
|
12
|
+
*/
|
|
13
|
+
export function normalizeCriteria(options) {
|
|
14
|
+
if (options === undefined) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const folded = options.tag === undefined ? (options.tags ?? []) : [options.tag, ...(options.tags ?? [])];
|
|
18
|
+
const tags = [...new Set(folded)];
|
|
19
|
+
if (options.name === undefined && tags.length === 0) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return { name: options.name, tags };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns whether two criteria address the same slot — equal name and the same tag set.
|
|
26
|
+
*/
|
|
27
|
+
export function criteriaEquals(left, right) {
|
|
28
|
+
return left.name === right.name && tagSetEquals(left.tags, right.tags);
|
|
29
|
+
}
|
|
30
|
+
/** Tag pairs are interned by di, so tag lists compare as identity sets, duplicates collapsed. */
|
|
31
|
+
function tagSetEquals(left, right) {
|
|
32
|
+
const leftSet = new Set(left);
|
|
33
|
+
const rightSet = new Set(right);
|
|
34
|
+
if (leftSet.size !== rightSet.size) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
for (const tag of leftSet) {
|
|
38
|
+
if (!rightSet.has(tag)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/** Returns the slot's own criteria, or `undefined` for an unconstrained slot. */
|
|
45
|
+
function criteriaOfSlot(slot) {
|
|
46
|
+
const tags = slot.tags ?? [];
|
|
47
|
+
if (slot.name === undefined && tags.length === 0) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return { name: slot.name, tags };
|
|
51
|
+
}
|
|
52
|
+
/** Returns whether an override's criteria address exactly this slot. */
|
|
53
|
+
function matchesSlot(criteria, slot) {
|
|
54
|
+
return criteria.name === slot.name && tagSetEquals(criteria.tags, slot.tags ?? []);
|
|
55
|
+
}
|
|
56
|
+
/** A readable rendering of criteria for error messages. */
|
|
57
|
+
function describeCriteria(criteria) {
|
|
58
|
+
const parts = [];
|
|
59
|
+
if (criteria.name !== undefined) {
|
|
60
|
+
parts.push(`name "${criteria.name}"`);
|
|
61
|
+
}
|
|
62
|
+
if (criteria.tags.length > 0) {
|
|
63
|
+
parts.push(`${String(criteria.tags.length)} tag(s)`);
|
|
64
|
+
}
|
|
65
|
+
return `slot with ${parts.join(" and ")}`;
|
|
66
|
+
}
|
|
67
|
+
/** Whether a slot can take a `usingAll` override — an unconstrained `injectAll` collects in order. */
|
|
68
|
+
function acceptsAll(slot) {
|
|
69
|
+
return slot.multi && slot.name === undefined && (slot.tags?.length ?? 0) === 0;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Binds a mock for every discovered dependency and returns the entries `mocks` reads.
|
|
73
|
+
*
|
|
74
|
+
* @remarks One shared mock per token for slots without a specific override — duplicate tokens across
|
|
75
|
+
* parameters share it — while an override carrying slot criteria supplies just its own slot; every
|
|
76
|
+
* constrained slot is also recorded under its own criteria so `mocks.get(token, options)` resolves.
|
|
77
|
+
* An override that matches nothing is an {@link UndeclaredDependencyError}; `.absent()` on a
|
|
78
|
+
* required slot or `.usingAll()` with no unconstrained `injectAll()` slot is an
|
|
79
|
+
* {@link OverrideMismatchError}.
|
|
80
|
+
*/
|
|
81
|
+
export function bindMocks(container, dependencies, overrides, mockFactory, exposed) {
|
|
82
|
+
if (exposed !== undefined) {
|
|
83
|
+
for (const key of overrides.keys()) {
|
|
84
|
+
if (typeof key === "function" && exposed.has(key)) {
|
|
85
|
+
throw new OverrideMismatchError(tokenName(key), "the class is exposed as a real collaborator, so it cannot also be mocked.");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const bound = new Map();
|
|
90
|
+
const consumed = new Set();
|
|
91
|
+
// One value per token-level lane and per slotted override, shared across duplicate slots.
|
|
92
|
+
const sharedValues = new Map();
|
|
93
|
+
const overrideValues = new Map();
|
|
94
|
+
const record = (token, entry) => {
|
|
95
|
+
let entries = bound.get(token);
|
|
96
|
+
if (entries === undefined) {
|
|
97
|
+
entries = [];
|
|
98
|
+
bound.set(token, entries);
|
|
99
|
+
}
|
|
100
|
+
// One entry per distinct slot criteria; duplicate slots share the first recording.
|
|
101
|
+
const duplicate = entries.some((candidate) => candidate.criteria === undefined || entry.criteria === undefined
|
|
102
|
+
? candidate.criteria === entry.criteria
|
|
103
|
+
: criteriaEquals(candidate.criteria, entry.criteria));
|
|
104
|
+
if (!duplicate) {
|
|
105
|
+
entries.push(entry);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const bindConstant = (slot, value) => {
|
|
109
|
+
const builder = container.bind(slot.token).toConstantValue(value);
|
|
110
|
+
if (slot.name !== undefined) {
|
|
111
|
+
builder.whenNamed(slot.name);
|
|
112
|
+
}
|
|
113
|
+
if (slot.tags !== undefined) {
|
|
114
|
+
for (const tag of slot.tags) {
|
|
115
|
+
builder.whenTagged(tag);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
for (const slot of dependencies) {
|
|
120
|
+
const list = overrides.get(slot.token) ?? [];
|
|
121
|
+
// A slotless `usingAll` applies only to the slots that can take it; other slots fall through.
|
|
122
|
+
const override = list.find((candidate) => candidate.criteria !== undefined && matchesSlot(candidate.criteria, slot)) ??
|
|
123
|
+
list.find((candidate) => candidate.criteria === undefined && (candidate.override.kind !== "all" || acceptsAll(slot)));
|
|
124
|
+
if (override !== undefined) {
|
|
125
|
+
consumed.add(override);
|
|
126
|
+
if (override.override.kind === "absent") {
|
|
127
|
+
if (!slot.optional && !slot.multi) {
|
|
128
|
+
throw new OverrideMismatchError(tokenName(slot.token), ".absent() targets a required dependency — only optional() or injectAll() slots can be absent.");
|
|
129
|
+
}
|
|
130
|
+
if (!overrideValues.has(override)) {
|
|
131
|
+
overrideValues.set(override, undefined);
|
|
132
|
+
}
|
|
133
|
+
record(slot.token, { criteria: override.criteria, value: undefined, kind: "absent" });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (override.override.kind === "all") {
|
|
137
|
+
if (!acceptsAll(slot)) {
|
|
138
|
+
throw new OverrideMismatchError(tokenName(slot.token), ".usingAll() supplies the elements of an unconstrained injectAll() slot — this slot is not one.");
|
|
139
|
+
}
|
|
140
|
+
if (!overrideValues.has(override)) {
|
|
141
|
+
const values = override.override.values;
|
|
142
|
+
overrideValues.set(override, values);
|
|
143
|
+
// Every element gets its own named slot: identical slots displace each other, and even a
|
|
144
|
+
// default-slot first element would be displaced-then-restored out of order by the second.
|
|
145
|
+
// An unconstrained resolveAll ignores slots and collects every binding in order.
|
|
146
|
+
for (const [index, value] of values.entries()) {
|
|
147
|
+
container
|
|
148
|
+
.bind(slot.token)
|
|
149
|
+
.toConstantValue(value)
|
|
150
|
+
.whenNamed(`${ALL_ELEMENT_SLOT_PREFIX}${String(index)}`);
|
|
151
|
+
}
|
|
152
|
+
record(slot.token, { criteria: override.criteria, value: values, kind: "all" });
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// A slotted override owns its own value; the token-level lane is shared across plain slots.
|
|
158
|
+
const lane = override?.override;
|
|
159
|
+
const kind = lane === undefined ? "auto" : lane.kind;
|
|
160
|
+
let value;
|
|
161
|
+
if (override !== undefined && override.criteria !== undefined) {
|
|
162
|
+
if (!overrideValues.has(override)) {
|
|
163
|
+
overrideValues.set(override, buildMock(lane, mockFactory));
|
|
164
|
+
}
|
|
165
|
+
value = overrideValues.get(override);
|
|
166
|
+
record(slot.token, { criteria: override.criteria, value, kind });
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (!sharedValues.has(slot.token)) {
|
|
170
|
+
sharedValues.set(slot.token, buildMock(lane, mockFactory));
|
|
171
|
+
record(slot.token, { criteria: undefined, value: sharedValues.get(slot.token), kind });
|
|
172
|
+
}
|
|
173
|
+
value = sharedValues.get(slot.token);
|
|
174
|
+
// A constrained slot on the shared lane is also addressable by its own criteria.
|
|
175
|
+
const slotCriteria = criteriaOfSlot(slot);
|
|
176
|
+
if (slotCriteria !== undefined) {
|
|
177
|
+
record(slot.token, { criteria: slotCriteria, value, kind });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
bindConstant(slot, value);
|
|
181
|
+
}
|
|
182
|
+
for (const [token, list] of overrides) {
|
|
183
|
+
for (const override of list) {
|
|
184
|
+
if (!consumed.has(override)) {
|
|
185
|
+
throw new UndeclaredDependencyError(tokenName(token), override.criteria === undefined
|
|
186
|
+
? override.override.kind === "all"
|
|
187
|
+
? "no unconstrained injectAll() slot"
|
|
188
|
+
: undefined
|
|
189
|
+
: describeCriteria(override.criteria));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return bound;
|
|
194
|
+
}
|
|
195
|
+
/** Returns the value bound for one lane: an override's value, a seeded auto-mock, or a plain auto-mock. */
|
|
196
|
+
function buildMock(override, mockFactory) {
|
|
197
|
+
if (override === undefined) {
|
|
198
|
+
return createAutoMock(mockFactory);
|
|
199
|
+
}
|
|
200
|
+
if (override.kind === "value") {
|
|
201
|
+
return override.value;
|
|
202
|
+
}
|
|
203
|
+
// The stub's seed is built here, once per compile, so beds never share seeded spies.
|
|
204
|
+
return createAutoMock(mockFactory, override.setup(mockFactory));
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=mock-binder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-binder.js","sourceRoot":"","sources":["../../src/discovery/mock-binder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** The error taxonomy `@codefast/di-testing` raises, each carrying a machine-readable `code`. */
|
|
2
|
+
/**
|
|
3
|
+
* Base class for every error the testing library throws, each carrying a machine-readable `code`.
|
|
4
|
+
*
|
|
5
|
+
* @remarks Mirrors `@codefast/di`'s own `DiError` shape rather than extending it, so a caller can
|
|
6
|
+
* catch testing-setup failures separately from resolution failures.
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class TestingError extends Error {
|
|
9
|
+
abstract readonly code: string;
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A scanned class takes constructor parameters but carries no `@injectable` metadata.
|
|
14
|
+
*/
|
|
15
|
+
export declare class NotInjectableError extends TestingError {
|
|
16
|
+
readonly code = "NOT_INJECTABLE";
|
|
17
|
+
readonly targetName: string;
|
|
18
|
+
constructor(targetName: string);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A `.mock(...)` override or a `mocks.get(...)` lookup named a token or slot the unit does not use.
|
|
22
|
+
*
|
|
23
|
+
* @remarks Almost always a typo or a stale token reference; failing loudly beats silently binding an
|
|
24
|
+
* unused constant or returning `undefined`.
|
|
25
|
+
*/
|
|
26
|
+
export declare class UndeclaredDependencyError extends TestingError {
|
|
27
|
+
readonly code = "UNDECLARED_DEPENDENCY";
|
|
28
|
+
readonly tokenName: string;
|
|
29
|
+
constructor(tokenName: string, detail?: string);
|
|
30
|
+
}
|
|
31
|
+
/** What a sealed entry was supplied with, naming the one cause a lookup error should report. */
|
|
32
|
+
export type SealedCause = "value" | "absent" | "all" | "exposed";
|
|
33
|
+
/**
|
|
34
|
+
* A `mocks.get(...)` lookup asked for a dependency that is not a retrievable mock.
|
|
35
|
+
*
|
|
36
|
+
* @remarks `.using()`, `.absent()`, and `.usingAll()` seal the value, and an exposed class is real —
|
|
37
|
+
* none of them carries a mock surface, so handing them back typed as `Mocked` would lie.
|
|
38
|
+
*/
|
|
39
|
+
export declare class SealedDependencyError extends TestingError {
|
|
40
|
+
readonly code = "SEALED_DEPENDENCY";
|
|
41
|
+
readonly tokenName: string;
|
|
42
|
+
constructor(tokenName: string, cause: SealedCause);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* An override whose shape does not fit the slot it targets — `.absent()` on a required dependency,
|
|
46
|
+
* or `.usingAll()` where no unconstrained `injectAll()` slot exists.
|
|
47
|
+
*/
|
|
48
|
+
export declare class OverrideMismatchError extends TestingError {
|
|
49
|
+
readonly code = "OVERRIDE_MISMATCH";
|
|
50
|
+
readonly tokenName: string;
|
|
51
|
+
constructor(tokenName: string, detail: string);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A sociable bed's exposure could not be honoured — the class is unreachable, already the unit, or
|
|
55
|
+
* was asked for without having been exposed.
|
|
56
|
+
*/
|
|
57
|
+
export declare class ExposureError extends TestingError {
|
|
58
|
+
readonly code = "EXPOSURE";
|
|
59
|
+
readonly targetName: string;
|
|
60
|
+
constructor(targetName: string, detail: string);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors/errors.ts"],"names":[],"mappings":"AAAA,iGAAiG;AAEjG;;;;;GAKG;AACH,8BAAsB,YAAa,SAAQ,KAAK;IAC9C,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAE/B,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,YAAY,UAAU,EAAE,MAAM,EAK7B;CACF;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,QAAQ,CAAC,IAAI,2BAA2B;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,YAAY,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAK7C;CACF;AAED,gGAAgG;AAChG,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;AASjE;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,YAAY,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAGhD;CACF;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,YAAY,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAG5C;CACF;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,QAAQ,CAAC,IAAI,cAAc;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,YAAY,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAG7C;CACF"}
|