@codefast/di-testing 0.1.1 → 0.1.3
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 +27 -0
- package/LICENSE +1 -1
- package/README.md +143 -66
- package/dist/discovery/dependency-scanner.d.ts +1 -2
- package/dist/discovery/dependency-scanner.js +1 -2
- package/dist/discovery/mock-binder.d.ts +1 -2
- package/dist/discovery/mock-binder.js +1 -2
- package/dist/errors/errors.d.ts +1 -2
- package/dist/errors/errors.js +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/mocking/auto-mock.d.ts +1 -2
- package/dist/mocking/auto-mock.js +1 -2
- package/dist/mocking/mock-factory.d.ts +1 -2
- package/dist/mocking/mock-factory.js +1 -2
- package/dist/mocking/spy.d.ts +1 -2
- package/dist/mocking/spy.js +1 -2
- package/dist/test-bed/bed-builder.d.ts +1 -2
- package/dist/test-bed/bed-builder.js +1 -2
- package/dist/test-bed/sociable-builder.d.ts +1 -2
- package/dist/test-bed/sociable-builder.js +1 -2
- package/dist/test-bed/solitary-builder.d.ts +1 -2
- package/dist/test-bed/solitary-builder.js +1 -2
- package/dist/test-bed/test-bed.d.ts +1 -2
- package/dist/test-bed/test-bed.js +1 -2
- package/dist/test-bed/unit-test-bed.d.ts +1 -2
- package/dist/test-bed/unit-test-bed.js +1 -2
- package/dist/types.d.ts +1 -2
- package/dist/types.js +1 -2
- package/package.json +7 -55
- package/dist/discovery/dependency-scanner.d.ts.map +0 -1
- package/dist/discovery/dependency-scanner.js.map +0 -1
- package/dist/discovery/mock-binder.d.ts.map +0 -1
- package/dist/discovery/mock-binder.js.map +0 -1
- package/dist/errors/errors.d.ts.map +0 -1
- package/dist/errors/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mocking/auto-mock.d.ts.map +0 -1
- package/dist/mocking/auto-mock.js.map +0 -1
- package/dist/mocking/mock-factory.d.ts.map +0 -1
- package/dist/mocking/mock-factory.js.map +0 -1
- package/dist/mocking/spy.d.ts.map +0 -1
- package/dist/mocking/spy.js.map +0 -1
- package/dist/test-bed/bed-builder.d.ts.map +0 -1
- package/dist/test-bed/bed-builder.js.map +0 -1
- package/dist/test-bed/sociable-builder.d.ts.map +0 -1
- package/dist/test-bed/sociable-builder.js.map +0 -1
- package/dist/test-bed/solitary-builder.d.ts.map +0 -1
- package/dist/test-bed/solitary-builder.js.map +0 -1
- package/dist/test-bed/test-bed.d.ts.map +0 -1
- package/dist/test-bed/test-bed.js.map +0 -1
- package/dist/test-bed/unit-test-bed.d.ts.map +0 -1
- package/dist/test-bed/unit-test-bed.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/discovery/dependency-scanner.ts +0 -80
- package/src/discovery/mock-binder.ts +0 -334
- package/src/errors/errors.ts +0 -119
- package/src/index.ts +0 -26
- package/src/mocking/auto-mock.ts +0 -243
- package/src/mocking/mock-factory.ts +0 -35
- package/src/mocking/spy.ts +0 -92
- package/src/test-bed/bed-builder.ts +0 -172
- package/src/test-bed/sociable-builder.ts +0 -148
- package/src/test-bed/solitary-builder.ts +0 -70
- package/src/test-bed/test-bed.ts +0 -64
- package/src/test-bed/unit-test-bed.ts +0 -161
- package/src/types.ts +0 -15
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
/** Binds a unit's discovered dependencies onto a container as mocks — the sole container coupling. */
|
|
2
|
-
|
|
3
|
-
import type { BindingTag, Constructor, Container, DependencyKey, DependencySlot, InjectOptions } from "@codefast/di";
|
|
4
|
-
import { slotName, tokenName } from "@codefast/di";
|
|
5
|
-
|
|
6
|
-
import { OverrideMismatchError, UndeclaredDependencyError } from "#/errors/errors";
|
|
7
|
-
import type { DeepPartial } from "#/mocking/auto-mock";
|
|
8
|
-
import { createAutoMock } from "#/mocking/auto-mock";
|
|
9
|
-
import type { MockFactory } from "#/mocking/mock-factory";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* How one dependency is supplied instead of a plain auto-mock.
|
|
13
|
-
*
|
|
14
|
-
* @remarks A stub's `setup` runs once per compile, so two beds built from one builder never share
|
|
15
|
-
* seeded spies.
|
|
16
|
-
*
|
|
17
|
-
* @since 0.1.0
|
|
18
|
-
*/
|
|
19
|
-
export type MockOverride =
|
|
20
|
-
| { readonly kind: "stub"; readonly setup: (mock: MockFactory) => unknown }
|
|
21
|
-
| { readonly kind: "value"; readonly value: unknown }
|
|
22
|
-
| { readonly kind: "absent" }
|
|
23
|
-
| { readonly kind: "all"; readonly values: ReadonlyArray<unknown> };
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Normalized slot criteria an override or a `mocks.get` lookup addresses one binding slot with.
|
|
27
|
-
*
|
|
28
|
-
* @since 0.1.0
|
|
29
|
-
*/
|
|
30
|
-
export interface SlotCriteria {
|
|
31
|
-
readonly name: string | undefined;
|
|
32
|
-
readonly tags: ReadonlyArray<BindingTag>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* One override registration: how a dependency is supplied, and which slot it targets (none = every
|
|
37
|
-
* slot of the token that has no more specific override).
|
|
38
|
-
*
|
|
39
|
-
* @since 0.1.0
|
|
40
|
-
*/
|
|
41
|
-
export interface SlottedOverride {
|
|
42
|
-
readonly criteria: SlotCriteria | undefined;
|
|
43
|
-
readonly override: MockOverride;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* What one bound entry was supplied with — only `auto` and `stub` entries carry a mock surface.
|
|
48
|
-
*
|
|
49
|
-
* @since 0.1.0
|
|
50
|
-
*/
|
|
51
|
-
export type BoundMockKind = "auto" | "stub" | "value" | "absent" | "all" | "exposed";
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* One bound value `mocks` can look up: its slot criteria, the value, and how it was supplied.
|
|
55
|
-
*
|
|
56
|
-
* @since 0.1.0
|
|
57
|
-
*/
|
|
58
|
-
export interface BoundMock {
|
|
59
|
-
readonly criteria: SlotCriteria | undefined;
|
|
60
|
-
readonly value: unknown;
|
|
61
|
-
readonly kind: BoundMockKind;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** The name prefix `usingAll` elements are bound under, so identical constants keep distinct slots. */
|
|
65
|
-
const ALL_ELEMENT_SLOT_PREFIX = "di-testing:all:";
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* One name and plain tag set, with di's reserved criterion folded into the name.
|
|
69
|
-
*
|
|
70
|
-
* @remarks di treats `{name: "x"}` and `{tag: slotName.of("x")}` as one slot, so addressing here
|
|
71
|
-
* must too — mirroring `bindingSlotToResolveOptions` in di.
|
|
72
|
-
*/
|
|
73
|
-
function foldReservedName(
|
|
74
|
-
name: string | undefined,
|
|
75
|
-
tags: ReadonlyArray<BindingTag>,
|
|
76
|
-
): { name: string | undefined; tags: ReadonlyArray<BindingTag> } {
|
|
77
|
-
const reserved = tags.find((tag) => tag.key === slotName);
|
|
78
|
-
if (reserved === undefined) {
|
|
79
|
-
return { name, tags };
|
|
80
|
-
}
|
|
81
|
-
return { name: name ?? (reserved.value as string), tags: tags.filter((tag) => tag.key !== slotName) };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Folds an `InjectOptions` into slot criteria, or `undefined` when it names no criterion.
|
|
86
|
-
*
|
|
87
|
-
* @remarks Tags are copied and deduplicated by identity — di interns each `(key, value)` pair, so a
|
|
88
|
-
* repeated pair is the same object. A reserved `slotName` criterion folds into `name`, so both
|
|
89
|
-
* spellings address one slot.
|
|
90
|
-
*
|
|
91
|
-
* @since 0.1.0
|
|
92
|
-
*/
|
|
93
|
-
export function normalizeCriteria(options: InjectOptions | undefined): SlotCriteria | undefined {
|
|
94
|
-
if (options === undefined) {
|
|
95
|
-
return undefined;
|
|
96
|
-
}
|
|
97
|
-
const merged = options.tag === undefined ? (options.tags ?? []) : [options.tag, ...(options.tags ?? [])];
|
|
98
|
-
const { name, tags } = foldReservedName(options.name, [...new Set(merged)]);
|
|
99
|
-
if (name === undefined && tags.length === 0) {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
return { name, tags };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Returns whether two criteria address the same slot — equal name and the same tag set.
|
|
107
|
-
*
|
|
108
|
-
* @since 0.1.0
|
|
109
|
-
*/
|
|
110
|
-
export function criteriaEquals(left: SlotCriteria, right: SlotCriteria): boolean {
|
|
111
|
-
return left.name === right.name && tagSetEquals(left.tags, right.tags);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Tag pairs are interned by di, so tag lists compare as identity sets, duplicates collapsed. */
|
|
115
|
-
function tagSetEquals(left: ReadonlyArray<BindingTag>, right: ReadonlyArray<BindingTag>): boolean {
|
|
116
|
-
const leftSet = new Set(left);
|
|
117
|
-
const rightSet = new Set(right);
|
|
118
|
-
if (leftSet.size !== rightSet.size) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
for (const tag of leftSet) {
|
|
122
|
-
if (!rightSet.has(tag)) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Returns the slot's own criteria, or `undefined` for an unconstrained slot. */
|
|
130
|
-
function criteriaOfSlot(slot: DependencySlot): SlotCriteria | undefined {
|
|
131
|
-
const { name, tags } = foldReservedName(slot.name, slot.tags ?? []);
|
|
132
|
-
if (name === undefined && tags.length === 0) {
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
return { name, tags };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Returns whether an override's criteria address exactly this slot. */
|
|
139
|
-
function matchesSlot(criteria: SlotCriteria, slot: DependencySlot): boolean {
|
|
140
|
-
const folded = foldReservedName(slot.name, slot.tags ?? []);
|
|
141
|
-
return criteria.name === folded.name && tagSetEquals(criteria.tags, folded.tags);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** A readable rendering of criteria for error messages. */
|
|
145
|
-
function describeCriteria(criteria: SlotCriteria): string {
|
|
146
|
-
const parts: Array<string> = [];
|
|
147
|
-
if (criteria.name !== undefined) {
|
|
148
|
-
parts.push(`name "${criteria.name}"`);
|
|
149
|
-
}
|
|
150
|
-
if (criteria.tags.length > 0) {
|
|
151
|
-
parts.push(`${String(criteria.tags.length)} tag(s)`);
|
|
152
|
-
}
|
|
153
|
-
return `slot with ${parts.join(" and ")}`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Whether a slot can take a `usingAll` override — an unconstrained `injectAll` collects in order. */
|
|
157
|
-
function acceptsAll(slot: DependencySlot): boolean {
|
|
158
|
-
return slot.multi && slot.name === undefined && (slot.tags?.length ?? 0) === 0;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Binds a mock for every discovered dependency and returns the entries `mocks` reads.
|
|
163
|
-
*
|
|
164
|
-
* @remarks One shared mock per token for slots without a specific override — duplicate tokens across
|
|
165
|
-
* parameters share it — while an override carrying slot criteria supplies just its own slot; every
|
|
166
|
-
* constrained slot is also recorded under its own criteria so `mocks.get(token, options)` resolves.
|
|
167
|
-
* An override that matches nothing is an {@link UndeclaredDependencyError}; `.absent()` on a
|
|
168
|
-
* required slot or `.usingAll()` with no unconstrained `injectAll()` slot is an
|
|
169
|
-
* {@link OverrideMismatchError}.
|
|
170
|
-
*
|
|
171
|
-
* @since 0.1.0
|
|
172
|
-
*/
|
|
173
|
-
export function bindMocks(
|
|
174
|
-
container: Container,
|
|
175
|
-
dependencies: ReadonlyArray<DependencySlot>,
|
|
176
|
-
overrides: ReadonlyMap<DependencyKey, ReadonlyArray<SlottedOverride>>,
|
|
177
|
-
mockFactory: MockFactory,
|
|
178
|
-
exposed?: ReadonlySet<Constructor>,
|
|
179
|
-
): ReadonlyMap<DependencyKey, ReadonlyArray<BoundMock>> {
|
|
180
|
-
if (exposed !== undefined) {
|
|
181
|
-
for (const key of overrides.keys()) {
|
|
182
|
-
if (typeof key === "function" && exposed.has(key)) {
|
|
183
|
-
throw new OverrideMismatchError(
|
|
184
|
-
tokenName(key),
|
|
185
|
-
"the class is exposed as a real collaborator, so it cannot also be mocked.",
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const bound = new Map<DependencyKey, Array<BoundMock>>();
|
|
192
|
-
const consumed = new Set<SlottedOverride>();
|
|
193
|
-
// One value per token-level lane and per slotted override, shared across duplicate slots.
|
|
194
|
-
const sharedValues = new Map<DependencyKey, unknown>();
|
|
195
|
-
const overrideValues = new Map<SlottedOverride, unknown>();
|
|
196
|
-
|
|
197
|
-
const record = (token: DependencyKey, entry: BoundMock): void => {
|
|
198
|
-
let entries = bound.get(token);
|
|
199
|
-
if (entries === undefined) {
|
|
200
|
-
entries = [];
|
|
201
|
-
bound.set(token, entries);
|
|
202
|
-
}
|
|
203
|
-
// One entry per distinct slot criteria; duplicate slots share the first recording.
|
|
204
|
-
const duplicate = entries.some((candidate) =>
|
|
205
|
-
candidate.criteria === undefined || entry.criteria === undefined
|
|
206
|
-
? candidate.criteria === entry.criteria
|
|
207
|
-
: criteriaEquals(candidate.criteria, entry.criteria),
|
|
208
|
-
);
|
|
209
|
-
if (!duplicate) {
|
|
210
|
-
entries.push(entry);
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const bindConstant = (slot: DependencySlot, value: unknown): void => {
|
|
215
|
-
const builder = container.bind(slot.token).toConstantValue(value);
|
|
216
|
-
if (slot.name !== undefined) {
|
|
217
|
-
builder.whenNamed(slot.name);
|
|
218
|
-
}
|
|
219
|
-
if (slot.tags !== undefined) {
|
|
220
|
-
for (const tag of slot.tags) {
|
|
221
|
-
builder.whenTagged(tag);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
for (const slot of dependencies) {
|
|
227
|
-
const list = overrides.get(slot.token) ?? [];
|
|
228
|
-
// A slotless `usingAll` applies only to the slots that can take it; other slots fall through.
|
|
229
|
-
const override =
|
|
230
|
-
list.find((candidate) => candidate.criteria !== undefined && matchesSlot(candidate.criteria, slot)) ??
|
|
231
|
-
list.find(
|
|
232
|
-
(candidate) => candidate.criteria === undefined && (candidate.override.kind !== "all" || acceptsAll(slot)),
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
if (override !== undefined) {
|
|
236
|
-
consumed.add(override);
|
|
237
|
-
|
|
238
|
-
if (override.override.kind === "absent") {
|
|
239
|
-
if (!slot.optional && !slot.multi) {
|
|
240
|
-
throw new OverrideMismatchError(
|
|
241
|
-
tokenName(slot.token),
|
|
242
|
-
".absent() targets a required dependency — only optional() or injectAll() slots can be absent.",
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
if (!overrideValues.has(override)) {
|
|
246
|
-
overrideValues.set(override, undefined);
|
|
247
|
-
}
|
|
248
|
-
record(slot.token, { criteria: override.criteria, value: undefined, kind: "absent" });
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (override.override.kind === "all") {
|
|
253
|
-
if (!acceptsAll(slot)) {
|
|
254
|
-
throw new OverrideMismatchError(
|
|
255
|
-
tokenName(slot.token),
|
|
256
|
-
".usingAll() supplies the elements of an unconstrained injectAll() slot — this slot is not one.",
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
if (!overrideValues.has(override)) {
|
|
260
|
-
const values = override.override.values;
|
|
261
|
-
overrideValues.set(override, values);
|
|
262
|
-
// Every element gets its own named slot: identical slots displace each other, and even a
|
|
263
|
-
// default-slot first element would be displaced-then-restored out of order by the second.
|
|
264
|
-
// An unconstrained resolveAll ignores slots and collects every binding in order.
|
|
265
|
-
for (const [index, value] of values.entries()) {
|
|
266
|
-
container
|
|
267
|
-
.bind(slot.token)
|
|
268
|
-
.toConstantValue(value)
|
|
269
|
-
.whenNamed(`${ALL_ELEMENT_SLOT_PREFIX}${String(index)}`);
|
|
270
|
-
}
|
|
271
|
-
record(slot.token, { criteria: override.criteria, value: values, kind: "all" });
|
|
272
|
-
}
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// A slotted override owns its own value; the token-level lane is shared across plain slots.
|
|
278
|
-
const lane = override?.override as Extract<MockOverride, { kind: "value" | "stub" }> | undefined;
|
|
279
|
-
const kind: BoundMockKind = lane === undefined ? "auto" : lane.kind;
|
|
280
|
-
let value: unknown;
|
|
281
|
-
if (override !== undefined && override.criteria !== undefined) {
|
|
282
|
-
if (!overrideValues.has(override)) {
|
|
283
|
-
overrideValues.set(override, buildMock(lane, mockFactory));
|
|
284
|
-
}
|
|
285
|
-
value = overrideValues.get(override);
|
|
286
|
-
record(slot.token, { criteria: override.criteria, value, kind });
|
|
287
|
-
} else {
|
|
288
|
-
if (!sharedValues.has(slot.token)) {
|
|
289
|
-
sharedValues.set(slot.token, buildMock(lane, mockFactory));
|
|
290
|
-
record(slot.token, { criteria: undefined, value: sharedValues.get(slot.token), kind });
|
|
291
|
-
}
|
|
292
|
-
value = sharedValues.get(slot.token);
|
|
293
|
-
// A constrained slot on the shared lane is also addressable by its own criteria.
|
|
294
|
-
const slotCriteria = criteriaOfSlot(slot);
|
|
295
|
-
if (slotCriteria !== undefined) {
|
|
296
|
-
record(slot.token, { criteria: slotCriteria, value, kind });
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
bindConstant(slot, value);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
for (const [token, list] of overrides) {
|
|
304
|
-
for (const override of list) {
|
|
305
|
-
if (!consumed.has(override)) {
|
|
306
|
-
throw new UndeclaredDependencyError(
|
|
307
|
-
tokenName(token),
|
|
308
|
-
override.criteria === undefined
|
|
309
|
-
? override.override.kind === "all"
|
|
310
|
-
? "no unconstrained injectAll() slot"
|
|
311
|
-
: undefined
|
|
312
|
-
: describeCriteria(override.criteria),
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return bound;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/** Returns the value bound for one lane: an override's value, a seeded auto-mock, or a plain auto-mock. */
|
|
322
|
-
function buildMock(
|
|
323
|
-
override: Extract<MockOverride, { kind: "value" | "stub" }> | undefined,
|
|
324
|
-
mockFactory: MockFactory,
|
|
325
|
-
): unknown {
|
|
326
|
-
if (override === undefined) {
|
|
327
|
-
return createAutoMock(mockFactory);
|
|
328
|
-
}
|
|
329
|
-
if (override.kind === "value") {
|
|
330
|
-
return override.value;
|
|
331
|
-
}
|
|
332
|
-
// The stub's seed is built here, once per compile, so beds never share seeded spies.
|
|
333
|
-
return createAutoMock(mockFactory, override.setup(mockFactory) as DeepPartial<unknown>);
|
|
334
|
-
}
|
package/src/errors/errors.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/** The error taxonomy `@codefast/di-testing` raises, each carrying a machine-readable `code`. */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base class for every error the testing library throws, each carrying a machine-readable `code`.
|
|
5
|
-
*
|
|
6
|
-
* @remarks Mirrors `@codefast/di`'s own `DiError` shape rather than extending it, so a caller can
|
|
7
|
-
* catch testing-setup failures separately from resolution failures.
|
|
8
|
-
*
|
|
9
|
-
* @since 0.1.0
|
|
10
|
-
*/
|
|
11
|
-
export abstract class TestingError extends Error {
|
|
12
|
-
abstract readonly code: string;
|
|
13
|
-
|
|
14
|
-
constructor(message: string) {
|
|
15
|
-
super(message);
|
|
16
|
-
this.name = this.constructor.name;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* A scanned class takes constructor parameters but carries no `@injectable` metadata.
|
|
22
|
-
*
|
|
23
|
-
* @since 0.1.0
|
|
24
|
-
*/
|
|
25
|
-
export class NotInjectableError extends TestingError {
|
|
26
|
-
readonly code = "NOT_INJECTABLE";
|
|
27
|
-
readonly targetName: string;
|
|
28
|
-
|
|
29
|
-
constructor(targetName: string) {
|
|
30
|
-
super(
|
|
31
|
-
`Cannot mock the dependencies of '${targetName}': its constructor takes parameters but the class is not decorated with @injectable(...), so those dependencies cannot be discovered. Decorate the class, or bind its collaborators manually with a Container.`,
|
|
32
|
-
);
|
|
33
|
-
this.targetName = targetName;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* A `.mock(...)` override or a `mocks.get(...)` lookup named a token or slot the unit does not use.
|
|
39
|
-
*
|
|
40
|
-
* @remarks Almost always a typo or a stale token reference; failing loudly beats silently binding an
|
|
41
|
-
* unused constant or returning `undefined`.
|
|
42
|
-
*
|
|
43
|
-
* @since 0.1.0
|
|
44
|
-
*/
|
|
45
|
-
export class UndeclaredDependencyError extends TestingError {
|
|
46
|
-
readonly code = "UNDECLARED_DEPENDENCY";
|
|
47
|
-
readonly tokenName: string;
|
|
48
|
-
|
|
49
|
-
constructor(tokenName: string, detail?: string) {
|
|
50
|
-
super(
|
|
51
|
-
`'${tokenName}'${detail === undefined ? "" : ` (${detail})`} is not a dependency of the class under test. Only tokens the class declares in its constructor or accessor injections can be mocked or retrieved.`,
|
|
52
|
-
);
|
|
53
|
-
this.tokenName = tokenName;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* What a sealed entry was supplied with, naming the one cause a lookup error should report.
|
|
59
|
-
*
|
|
60
|
-
* @since 0.1.0
|
|
61
|
-
*/
|
|
62
|
-
export type SealedCause = "value" | "absent" | "all" | "exposed";
|
|
63
|
-
|
|
64
|
-
const SEALED_MESSAGES: Readonly<Record<SealedCause, string>> = {
|
|
65
|
-
value: "it was supplied with .using(), so it has no mock surface. Use the reference the test passed in.",
|
|
66
|
-
absent: "it was declared absent with .absent(), so there is nothing to retrieve.",
|
|
67
|
-
all: "its elements were supplied with .usingAll(). Use the references the test passed in.",
|
|
68
|
-
exposed: "it is exposed as a real collaborator. Retrieve it with bed.exposed(Class) instead.",
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A `mocks.get(...)` lookup asked for a dependency that is not a retrievable mock.
|
|
73
|
-
*
|
|
74
|
-
* @remarks `.using()`, `.absent()`, and `.usingAll()` seal the value, and an exposed class is real —
|
|
75
|
-
* none of them carries a mock surface, so handing them back typed as `Mocked` would lie.
|
|
76
|
-
*
|
|
77
|
-
* @since 0.1.0
|
|
78
|
-
*/
|
|
79
|
-
export class SealedDependencyError extends TestingError {
|
|
80
|
-
readonly code = "SEALED_DEPENDENCY";
|
|
81
|
-
readonly tokenName: string;
|
|
82
|
-
|
|
83
|
-
constructor(tokenName: string, cause: SealedCause) {
|
|
84
|
-
super(`'${tokenName}' is not a retrievable mock: ${SEALED_MESSAGES[cause]}`);
|
|
85
|
-
this.tokenName = tokenName;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* An override whose shape does not fit the slot it targets — `.absent()` on a required dependency,
|
|
91
|
-
* or `.usingAll()` where no unconstrained `injectAll()` slot exists.
|
|
92
|
-
*
|
|
93
|
-
* @since 0.1.0
|
|
94
|
-
*/
|
|
95
|
-
export class OverrideMismatchError extends TestingError {
|
|
96
|
-
readonly code = "OVERRIDE_MISMATCH";
|
|
97
|
-
readonly tokenName: string;
|
|
98
|
-
|
|
99
|
-
constructor(tokenName: string, detail: string) {
|
|
100
|
-
super(`'${tokenName}': ${detail}`);
|
|
101
|
-
this.tokenName = tokenName;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* A sociable bed's exposure could not be honoured — the class is unreachable, already the unit, or
|
|
107
|
-
* was asked for without having been exposed.
|
|
108
|
-
*
|
|
109
|
-
* @since 0.1.0
|
|
110
|
-
*/
|
|
111
|
-
export class ExposureError extends TestingError {
|
|
112
|
-
readonly code = "EXPOSURE";
|
|
113
|
-
readonly targetName: string;
|
|
114
|
-
|
|
115
|
-
constructor(targetName: string, detail: string) {
|
|
116
|
-
super(`'${targetName}': ${detail}`);
|
|
117
|
-
this.targetName = targetName;
|
|
118
|
-
}
|
|
119
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/** Solitary and sociable auto-mocking test beds for `@codefast/di`. */
|
|
2
|
-
|
|
3
|
-
export { TestBed } from "#/test-bed/test-bed";
|
|
4
|
-
export type { TestBedStatic } from "#/test-bed/test-bed";
|
|
5
|
-
export type { MockOverrideBuilder, PreparedBed, TestBedOptions } from "#/test-bed/bed-builder";
|
|
6
|
-
export type { SolitaryTestBedBuilder } from "#/test-bed/solitary-builder";
|
|
7
|
-
export type { SociableTestBedBuilder } from "#/test-bed/sociable-builder";
|
|
8
|
-
export type { SociableUnitTestBed, UnitReference, UnitTestBed } from "#/test-bed/unit-test-bed";
|
|
9
|
-
|
|
10
|
-
export { createAutoMock, MOCK_RESET } from "#/mocking/auto-mock";
|
|
11
|
-
export type { DeepPartial, Mocked } from "#/mocking/auto-mock";
|
|
12
|
-
export { defaultMockFactory } from "#/mocking/mock-factory";
|
|
13
|
-
export type { MockFactory, MockFunction } from "#/mocking/mock-factory";
|
|
14
|
-
export { createSpy } from "#/mocking/spy";
|
|
15
|
-
export type { Spy, SpyResult, SpyState } from "#/mocking/spy";
|
|
16
|
-
|
|
17
|
-
export type { InjectionIdentifier } from "#/types";
|
|
18
|
-
export {
|
|
19
|
-
ExposureError,
|
|
20
|
-
NotInjectableError,
|
|
21
|
-
OverrideMismatchError,
|
|
22
|
-
SealedDependencyError,
|
|
23
|
-
TestingError,
|
|
24
|
-
UndeclaredDependencyError,
|
|
25
|
-
} from "#/errors/errors";
|
|
26
|
-
export type { SealedCause } from "#/errors/errors";
|