@codefast/di-testing 0.1.4 → 0.3.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/CHANGELOG.md +86 -0
- package/README.md +47 -23
- package/dist/discovery/mock-binder.js +4 -3
- package/dist/errors/errors.d.ts +12 -0
- package/dist/errors/errors.js +14 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/test-bed/bed-builder.d.ts +5 -5
- package/dist/test-bed/bed-builder.js +3 -5
- package/dist/test-bed/sociable-builder.js +2 -1
- package/dist/test-bed/test-bed.d.ts +23 -13
- package/dist/test-bed/test-bed.js +30 -12
- package/dist/test-bed/unit-test-bed.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,91 @@
|
|
|
1
1
|
# @codefast/di-testing
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#999](https://github.com/codefastlabs/codefast/pull/999) Requires `@codefast/di` 0.12, whose chains declare a binding's slot before its strategy; the test beds bind their mocks
|
|
8
|
+
and real collaborators in that order.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- [#1005](https://github.com/codefastlabs/codefast/pull/1005) **Breaking for deep subpath imports only** — the root entry `@codefast/di` is unchanged. `src/` now follows three rules,
|
|
13
|
+
and the published subpaths follow `src/` with no exception: a directory names a family of two or more modules and a lone
|
|
14
|
+
module sits flat at its layer; a directory carries the topic and a file its role, so no file repeats its directory's
|
|
15
|
+
name; and the exports map is the source tree, so the `strip` that kept the introspection modules at flat specifiers is
|
|
16
|
+
gone.
|
|
17
|
+
|
|
18
|
+
Moves: the `MetadataReader` verification leaves `resolution/cache/class-introspector` for `metadata/verify` — it was the
|
|
19
|
+
one value import pointing up from `metadata/` into `resolution/`; the `RESOLUTION_DIAGNOSTICS` channel leaves `errors/`
|
|
20
|
+
for `introspection/diagnostics`; the fluent chain's contract leaves `core/binding` for `core/binding-builders`, and the
|
|
21
|
+
class implementing it is `container/binding-chain`; `effectiveBindingScope` folds into `core/binding`.
|
|
22
|
+
|
|
23
|
+
Renamed specifiers: `./errors/errors` → `./errors`; `./errors/diagnostics` → `./introspection/diagnostics`;
|
|
24
|
+
`./ambient/active-container` → `./ambient-container`; `./container/binding-builders` → `./container/binding-chain`;
|
|
25
|
+
`./core/binding-scope` → gone (`effectiveBindingScope` is in `./core/binding`); `./injection/resolve-options` →
|
|
26
|
+
`./injection/dependency-slot`;
|
|
27
|
+
`./metadata/{metadata-keys,metadata-types,metadata-reader-token,symbol-metadata-reader,verifying-metadata-reader}` →
|
|
28
|
+
`./metadata/{keys,types,reader-token,symbol-reader,verifying-reader}`; `./lifecycle/{lifecycle-manager,scope-manager}` →
|
|
29
|
+
`./lifecycle/{hooks,scopes}`; `./decorators/{lifecycle-decorators,decorator-metadata}` →
|
|
30
|
+
`./decorators/{lifecycle,metadata-record}`; `./resolution/path/resolution-path` → `./resolution/path`;
|
|
31
|
+
`./resolution/plan/{instantiation-plan,plan-codegen}` → `./resolution/plan/{compiler,codegen}`;
|
|
32
|
+
`./resolution/select/binding-select` → `./resolution/select/candidates`; `./resolution/cache/binding-lookup-cache` →
|
|
33
|
+
`./resolution/cache/lookup`; `./{inspector,explanation,dependency-graph,graph-adapters/*}` →
|
|
34
|
+
`./introspection/{inspector,explanation,dependency-graph,graph-adapters/*}`. New: `./core/binding-builders`,
|
|
35
|
+
`./metadata/verify`.
|
|
36
|
+
|
|
37
|
+
`@codefast/di-testing` follows the one specifier it imports, `metadata/verifying-reader`.
|
|
38
|
+
|
|
39
|
+
- Updated dependencies:
|
|
40
|
+
- @codefast/di@0.12.0
|
|
41
|
+
|
|
42
|
+
## 0.2.0
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- [#952](https://github.com/codefastlabs/codefast/pull/952) A suite now states its mock backend once, and a test bed can no longer be typed against one backend and built from
|
|
47
|
+
another. `createTestBed({ mockFactory, metadataReader? })` returns the entry point every bed begins from, its `Backend`
|
|
48
|
+
inferred from the factory; `TestBed.solitary(target)` and `TestBed.sociable(target)` take the target only. The `TestBed`
|
|
49
|
+
export is `createTestBed({ mockFactory: defaultMockFactory })`, the built-in spy with no framework needed.
|
|
50
|
+
|
|
51
|
+
Before, the backend was chosen per call — `TestBed.solitary(Unit, { mockFactory: () => vi.fn() })` — and
|
|
52
|
+
`TestBed.solitary<Unit, SinonStub>(Unit)` with no factory type-checked, typed every mock as `SinonStub`, and built the
|
|
53
|
+
default spy. Migrate by creating the suite's entry point once:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createTestBed } from "@codefast/di-testing";
|
|
57
|
+
import { vi } from "vitest";
|
|
58
|
+
|
|
59
|
+
export const TestBed = createTestBed({ mockFactory: () => vi.fn() });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`TestBedOptions<Backend>` configures `createTestBed` and requires `mockFactory`; `TestBedStatic<Backend>` names the
|
|
63
|
+
entry point's backend and has no default. `createTestBed` throws the new `MissingMockFactoryError`
|
|
64
|
+
(`MISSING_MOCK_FACTORY`) for a caller past the types who passes no factory, instead of standing the built-in spy in for
|
|
65
|
+
a backend nobody chose.
|
|
66
|
+
|
|
67
|
+
- [#976](https://github.com/codefastlabs/codefast/pull/976) `engines.node` is now `>=24.0.0`, up from `>=22.12.0`, and Node 22 is no longer supported. Node 24.0.0 is the first
|
|
68
|
+
release with explicit resource management built in (`using`, `await using`, `DisposableStack`, `AsyncDisposableStack`,
|
|
69
|
+
`SuppressedError`) and all of ES2025, so the packages use both as the platform ships them instead of shimming them for
|
|
70
|
+
an older line, and the CI matrix runs the unit suite on 24.0.0 itself. Move to Node 24, or stay on the current minor
|
|
71
|
+
while a deployment still runs Node 22.
|
|
72
|
+
|
|
73
|
+
### Patch Changes
|
|
74
|
+
|
|
75
|
+
- [#976](https://github.com/codefastlabs/codefast/pull/976) The README states what a program needs for the declarations' disposal members: the explicit resource management types,
|
|
76
|
+
which no numbered `lib` declares before ES2027. `@types/node` 24 or later loads them, and so does `ESNext.Disposable` in
|
|
77
|
+
`lib`. Without either, TypeScript 7 fails inside `container.d.ts` with TS2550 under `skipLibCheck: false`, and at
|
|
78
|
+
`await using` with TS2318. `@codefast/di-testing` drops a `/// <reference lib="esnext.disposable" />` that TypeScript 7
|
|
79
|
+
stripped from its emitted declarations anyway, and takes the lib from its `tsconfig.json` instead.
|
|
80
|
+
|
|
81
|
+
- [#956](https://github.com/codefastlabs/codefast/pull/956) `README.md` no longer quotes the `@codefast/di` peer range, which had fallen behind `package.json` — the release tooling
|
|
82
|
+
moves that range, so the manifest is the one place it is stated.
|
|
83
|
+
|
|
84
|
+
- [#965](https://github.com/codefastlabs/codefast/pull/965) `README.md` now states TypeScript 7 or later as the floor for the package's types — the one compiler every `@codefast/*`
|
|
85
|
+
package is built and checked with. No code or declaration changed.
|
|
86
|
+
- Updated dependencies:
|
|
87
|
+
- @codefast/di@0.11.0
|
|
88
|
+
|
|
3
89
|
## 0.1.4
|
|
4
90
|
|
|
5
91
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -19,11 +19,13 @@ production — and you assert against the mocks the bed created.
|
|
|
19
19
|
and builds a mock for each — no per-collaborator `bind(...).toConstantValue(...)`.
|
|
20
20
|
- **Real instance, real wiring.** The unit is constructed through a container, so `@postConstruct`, accessor injection,
|
|
21
21
|
and `@preDestroy` run exactly as in production.
|
|
22
|
-
- **Zero test-framework dependency.** The default
|
|
23
|
-
`() =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
- **Zero test-framework dependency.** The default `TestBed` mocks with a small built-in spy. Create your suite's own
|
|
23
|
+
with `createTestBed({ mockFactory: () => vi.fn() })` — or `jest.fn`, `() => sinon.stub()` — to build every mock from
|
|
24
|
+
that backend and use its matchers instead.
|
|
25
|
+
- **Backend-typed lookups.** The factory's return type flows through every bed the entry point begins: with
|
|
26
|
+
`() => vi.fn()`, `mocks.get(EmailToken).send` carries Vitest's own mock surface (`mockReturnValueOnce`, `mockClear`,
|
|
27
|
+
and so on), with no adapter package and no module augmentation. A bed can have no other backend than its entry point,
|
|
28
|
+
so a mock is never typed as one backend and built from another.
|
|
27
29
|
|
|
28
30
|
## Installation
|
|
29
31
|
|
|
@@ -31,17 +33,21 @@ production — and you assert against the mocks the bed created.
|
|
|
31
33
|
pnpm add -D @codefast/di-testing
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
`@codefast/di-testing` requires Node.js
|
|
35
|
-
TypeScript setup: native Stage 3 decorators, `experimentalDecorators` off
|
|
36
|
-
|
|
36
|
+
`@codefast/di-testing` requires Node.js 24 or later, TypeScript 7 or later, and a peer install of `@codefast/di`, with
|
|
37
|
+
the same TypeScript setup: native Stage 3 decorators, `experimentalDecorators` off, and the explicit resource management
|
|
38
|
+
types, from `@types/node` 24 or later or from `ESNext.Disposable` in `lib`. The package is published on 0.x and
|
|
39
|
+
versioned on its own track: breaking changes ship as minor versions, so pin the minor version when you need stability.
|
|
37
40
|
|
|
38
41
|
## Quick start
|
|
39
42
|
|
|
40
43
|
```ts
|
|
41
44
|
import { injectable, token } from "@codefast/di";
|
|
42
|
-
import {
|
|
45
|
+
import { createTestBed } from "@codefast/di-testing";
|
|
43
46
|
import { expect, it, vi } from "vitest";
|
|
44
47
|
|
|
48
|
+
// The suite's entry point on Vitest spies, so matchers and mockReturnValue work on every mock.
|
|
49
|
+
const TestBed = createTestBed({ mockFactory: () => vi.fn() });
|
|
50
|
+
|
|
45
51
|
interface UserService {
|
|
46
52
|
findUser(id: string): { id: string; email: string };
|
|
47
53
|
}
|
|
@@ -73,7 +79,7 @@ class OrderProcessor {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
it("charges then emails a confirmation", () => {
|
|
76
|
-
const { unit, mocks } = TestBed.solitary(OrderProcessor
|
|
82
|
+
const { unit, mocks } = TestBed.solitary(OrderProcessor)
|
|
77
83
|
.mock(UserServiceToken)
|
|
78
84
|
.stub((fn) => ({ findUser: fn().mockReturnValue({ id: "u1", email: "alice@example.com" }) }))
|
|
79
85
|
.compile();
|
|
@@ -85,8 +91,8 @@ it("charges then emails a confirmation", () => {
|
|
|
85
91
|
});
|
|
86
92
|
```
|
|
87
93
|
|
|
88
|
-
The zero-dependency default
|
|
89
|
-
|
|
94
|
+
The zero-dependency default is the `TestBed` export itself. Assert against the built-in spy's `.mock.calls` and stub
|
|
95
|
+
with `.mockReturnValue()`:
|
|
90
96
|
|
|
91
97
|
```ts
|
|
92
98
|
import { TestBed } from "@codefast/di-testing";
|
|
@@ -97,14 +103,29 @@ unit.placeOrder("u1", 42);
|
|
|
97
103
|
assert.deepEqual(mocks.get(PaymentGatewayToken).charge.mock.calls[0], ["u1", 42]);
|
|
98
104
|
```
|
|
99
105
|
|
|
100
|
-
##
|
|
106
|
+
## Entry points
|
|
107
|
+
|
|
108
|
+
A suite states its mock backend once. `createTestBed(options)` returns the entry point every bed in the suite begins
|
|
109
|
+
from — typically created in one shared test-support module:
|
|
101
110
|
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
```ts
|
|
112
|
+
import { createTestBed } from "@codefast/di-testing";
|
|
113
|
+
import { vi } from "vitest";
|
|
114
|
+
|
|
115
|
+
export const TestBed = createTestBed({ mockFactory: () => vi.fn() });
|
|
116
|
+
```
|
|
104
117
|
|
|
105
|
-
- `mockFactory
|
|
118
|
+
- `mockFactory: () => spy` — required: the spy backend each auto-mock is built from. Its return type is the entry
|
|
119
|
+
point's backend and types every bed it begins. Pass `defaultMockFactory` for the built-in spy.
|
|
106
120
|
- `metadataReader?: MetadataReader` — the reader dependencies are discovered through. Defaults to di's reader.
|
|
107
121
|
|
|
122
|
+
The `TestBed` export is `createTestBed({ mockFactory: defaultMockFactory })`: the built-in spy, no framework needed.
|
|
123
|
+
|
|
124
|
+
## Solitary beds
|
|
125
|
+
|
|
126
|
+
`TestBed.solitary(target)` begins a bed for `target` and auto-mocks every dependency it declares with the entry point's
|
|
127
|
+
backend. Nothing is instantiated until you call `compile()`.
|
|
128
|
+
|
|
108
129
|
The builder records overrides, then compiles:
|
|
109
130
|
|
|
110
131
|
- `.mock(token).stub((fn) => stub)` — bind a partial stub built from the active spy factory; unlisted members stay
|
|
@@ -127,14 +148,17 @@ The builder records overrides, then compiles:
|
|
|
127
148
|
## Sociable beds
|
|
128
149
|
|
|
129
150
|
A sociable bed keeps chosen collaborators real while everything else stays mocked — a unit test over a small real
|
|
130
|
-
subtree, not an integration test. `TestBed.sociable(target
|
|
131
|
-
`.expose()`, because a sociable bed with nothing exposed is a solitary bed.
|
|
151
|
+
subtree, not an integration test. `TestBed.sociable(target)` mocks with the entry point's backend like `solitary` and
|
|
152
|
+
returns only `.expose()`, because a sociable bed with nothing exposed is a solitary bed.
|
|
132
153
|
|
|
133
154
|
```ts
|
|
134
155
|
import { injectable, token } from "@codefast/di";
|
|
135
|
-
import {
|
|
156
|
+
import { createTestBed } from "@codefast/di-testing";
|
|
136
157
|
import { expect, it, vi } from "vitest";
|
|
137
158
|
|
|
159
|
+
// The suite's entry point on Vitest spies, so matchers and mockReturnValue work on every mock.
|
|
160
|
+
const TestBed = createTestBed({ mockFactory: () => vi.fn() });
|
|
161
|
+
|
|
138
162
|
interface TaxPolicy {
|
|
139
163
|
rateFor(currency: string): number;
|
|
140
164
|
}
|
|
@@ -160,7 +184,7 @@ class CheckoutService {
|
|
|
160
184
|
}
|
|
161
185
|
|
|
162
186
|
it("prices through the real PricingService over a mocked tax boundary", () => {
|
|
163
|
-
const bed = TestBed.sociable(CheckoutService
|
|
187
|
+
const bed = TestBed.sociable(CheckoutService)
|
|
164
188
|
.expose(PricingService)
|
|
165
189
|
.mock(TaxPolicyToken)
|
|
166
190
|
.stub((fn) => ({ rateFor: fn().mockReturnValue(0.1) }))
|
|
@@ -206,10 +230,10 @@ it("prices through the real PricingService over a mocked tax boundary", () => {
|
|
|
206
230
|
- `dispose()` — run the unit's `@preDestroy` hooks and dispose the container.
|
|
207
231
|
|
|
208
232
|
The bed implements `AsyncDisposable`, so `await using bed = TestBed.solitary(X).compile()` disposes it at the end of the
|
|
209
|
-
block
|
|
233
|
+
block.
|
|
210
234
|
|
|
211
235
|
The lower-level pieces are exported too: `createAutoMock`, `createSpy`, `defaultMockFactory`, and the `Mocked`,
|
|
212
|
-
`DeepPartial`, `MockFactory`, and `
|
|
236
|
+
`DeepPartial`, `MockFactory`, `Spy`, and `TestBedOptions` types.
|
|
213
237
|
|
|
214
238
|
## Errors
|
|
215
239
|
|
|
@@ -227,7 +251,7 @@ extending it, so setup failures can be caught separately from resolution failure
|
|
|
227
251
|
## Documentation
|
|
228
252
|
|
|
229
253
|
- [Rendered docs on codefastlabs.com](https://codefastlabs.com/docs/di-testing)
|
|
230
|
-
- [`@codefast/di`](../di/README.md) — the container this package builds on, including its `
|
|
254
|
+
- [`@codefast/di`](../di/README.md) — the container this package builds on, including its specification under `spec/`.
|
|
231
255
|
- [`CHANGELOG.md`](./CHANGELOG.md) — release history.
|
|
232
256
|
|
|
233
257
|
## Contributing
|
|
@@ -127,7 +127,7 @@ export function bindMocks(container, dependencies, overrides, mockFactory, expos
|
|
|
127
127
|
}
|
|
128
128
|
};
|
|
129
129
|
const bindConstant = (slot, value) => {
|
|
130
|
-
const builder = container.bind(slot.token)
|
|
130
|
+
const builder = container.bind(slot.token);
|
|
131
131
|
if (slot.name !== undefined) {
|
|
132
132
|
builder.whenNamed(slot.name);
|
|
133
133
|
}
|
|
@@ -136,6 +136,7 @@ export function bindMocks(container, dependencies, overrides, mockFactory, expos
|
|
|
136
136
|
builder.whenTagged(tag);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
builder.toConstantValue(value);
|
|
139
140
|
};
|
|
140
141
|
for (const slot of dependencies) {
|
|
141
142
|
const list = overrides.get(slot.token) ?? [];
|
|
@@ -167,8 +168,8 @@ export function bindMocks(container, dependencies, overrides, mockFactory, expos
|
|
|
167
168
|
for (const [index, value] of values.entries()) {
|
|
168
169
|
container
|
|
169
170
|
.bind(slot.token)
|
|
170
|
-
.
|
|
171
|
-
.
|
|
171
|
+
.whenNamed(`${ALL_ELEMENT_SLOT_PREFIX}${String(index)}`)
|
|
172
|
+
.toConstantValue(value);
|
|
172
173
|
}
|
|
173
174
|
record(slot.token, { criteria: override.criteria, value: values, kind: "all" });
|
|
174
175
|
}
|
package/dist/errors/errors.d.ts
CHANGED
|
@@ -21,6 +21,18 @@ export declare class NotInjectableError extends TestingError {
|
|
|
21
21
|
readonly targetName: string;
|
|
22
22
|
constructor(targetName: string);
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* A `createTestBed(...)` call with no mock factory to build its beds' mocks from.
|
|
26
|
+
*
|
|
27
|
+
* @remarks The types require the factory; this reaches a caller the compiler cannot, and stops the
|
|
28
|
+
* built-in spy from standing in for a backend nobody chose.
|
|
29
|
+
*
|
|
30
|
+
* @since 0.2.0
|
|
31
|
+
*/
|
|
32
|
+
export declare class MissingMockFactoryError extends TestingError {
|
|
33
|
+
readonly code = "MISSING_MOCK_FACTORY";
|
|
34
|
+
constructor();
|
|
35
|
+
}
|
|
24
36
|
/**
|
|
25
37
|
* A `.mock(...)` override or a `mocks.get(...)` lookup named a token or slot the unit does not use.
|
|
26
38
|
*
|
package/dist/errors/errors.js
CHANGED
|
@@ -26,6 +26,20 @@ export class NotInjectableError extends TestingError {
|
|
|
26
26
|
this.targetName = targetName;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* A `createTestBed(...)` call with no mock factory to build its beds' mocks from.
|
|
31
|
+
*
|
|
32
|
+
* @remarks The types require the factory; this reaches a caller the compiler cannot, and stops the
|
|
33
|
+
* built-in spy from standing in for a backend nobody chose.
|
|
34
|
+
*
|
|
35
|
+
* @since 0.2.0
|
|
36
|
+
*/
|
|
37
|
+
export class MissingMockFactoryError extends TestingError {
|
|
38
|
+
code = "MISSING_MOCK_FACTORY";
|
|
39
|
+
constructor() {
|
|
40
|
+
super("createTestBed() needs a mockFactory to build every bed's mocks from: pass defaultMockFactory for the built-in spy, or a factory such as () => vi.fn().");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
29
43
|
/**
|
|
30
44
|
* A `.mock(...)` override or a `mocks.get(...)` lookup named a token or slot the unit does not use.
|
|
31
45
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Solitary and sociable auto-mocking test beds for `@codefast/di`. */
|
|
2
|
-
export { TestBed } from "#test-bed/test-bed";
|
|
2
|
+
export { createTestBed, TestBed } from "#test-bed/test-bed";
|
|
3
3
|
export type { TestBedStatic } from "#test-bed/test-bed";
|
|
4
4
|
export type { MockOverrideBuilder, PreparedBed, TestBedOptions } from "#test-bed/bed-builder";
|
|
5
5
|
export type { SolitaryTestBedBuilder } from "#test-bed/solitary-builder";
|
|
@@ -12,5 +12,5 @@ export type { MockFactory, MockFunction } from "#mocking/mock-factory";
|
|
|
12
12
|
export { createSpy } from "#mocking/spy";
|
|
13
13
|
export type { Spy, SpyResult, SpyState } from "#mocking/spy";
|
|
14
14
|
export type { InjectionIdentifier } from "#types";
|
|
15
|
-
export { ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#errors/errors";
|
|
15
|
+
export { MissingMockFactoryError, ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#errors/errors";
|
|
16
16
|
export type { SealedCause } from "#errors/errors";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Solitary and sociable auto-mocking test beds for `@codefast/di`. */
|
|
2
|
-
export { TestBed } from "#test-bed/test-bed";
|
|
2
|
+
export { createTestBed, TestBed } from "#test-bed/test-bed";
|
|
3
3
|
export { createAutoMock, MOCK_RESET } from "#mocking/auto-mock";
|
|
4
4
|
export { defaultMockFactory } from "#mocking/mock-factory";
|
|
5
5
|
export { createSpy } from "#mocking/spy";
|
|
6
|
-
export { ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#errors/errors";
|
|
6
|
+
export { MissingMockFactoryError, ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#errors/errors";
|
|
@@ -6,16 +6,16 @@ import type { MockFactory, MockFunction } from "#mocking/mock-factory";
|
|
|
6
6
|
import type { Spy } from "#mocking/spy";
|
|
7
7
|
import type { InjectionIdentifier } from "#types";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* What `createTestBed` is configured with: the mock backend and the reader every bed it begins shares.
|
|
10
10
|
*
|
|
11
11
|
* @typeParam Backend - The spy type the mock factory produces; it flows into every `Mocked` member,
|
|
12
12
|
* `mocks.get`, and the `.stub` callback, so `() => vi.fn()` yields Vitest's own mock typing.
|
|
13
13
|
*
|
|
14
14
|
* @since 0.1.0
|
|
15
15
|
*/
|
|
16
|
-
export interface TestBedOptions<Backend extends MockFunction
|
|
17
|
-
/** Spy factory each auto-mock property is materialized with;
|
|
18
|
-
readonly mockFactory
|
|
16
|
+
export interface TestBedOptions<Backend extends MockFunction> {
|
|
17
|
+
/** Spy factory each auto-mock property is materialized with; its return type is the beds' backend. */
|
|
18
|
+
readonly mockFactory: MockFactory<Backend>;
|
|
19
19
|
/** Reader the dependency scan and the compile container both consult; defaults to di's reader. */
|
|
20
20
|
readonly metadataReader?: MetadataReader | undefined;
|
|
21
21
|
}
|
|
@@ -74,7 +74,7 @@ export declare abstract class BedBuilder<Class, Backend extends MockFunction = S
|
|
|
74
74
|
protected readonly reader: MetadataReader;
|
|
75
75
|
protected readonly mockFactory: MockFactory<Backend>;
|
|
76
76
|
protected readonly overrides: ReadonlyMap<DependencyKey, ReadonlyArray<SlottedOverride>>;
|
|
77
|
-
constructor(target: Constructor<Class>, options
|
|
77
|
+
constructor(target: Constructor<Class>, options: TestBedOptions<Backend>);
|
|
78
78
|
/**
|
|
79
79
|
* Replaces the auto-mock for one dependency with a hand-written stub or a concrete value.
|
|
80
80
|
*
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/** The override-recording core both test-bed builders extend. */
|
|
2
2
|
import { defaultMetadataReader } from "@codefast/di";
|
|
3
|
-
import { verifyingMetadataReader } from "@codefast/di/metadata/verifying-
|
|
3
|
+
import { verifyingMetadataReader } from "@codefast/di/metadata/verifying-reader";
|
|
4
4
|
import { criteriaEquals, normalizeCriteria } from "#discovery/mock-binder";
|
|
5
|
-
import { defaultMockFactory } from "#mocking/mock-factory";
|
|
6
5
|
/**
|
|
7
6
|
* The shared builder core: records overrides, resolves the reader and mock factory, and owns the
|
|
8
7
|
* compile template that disposes the container when a build fails.
|
|
@@ -23,9 +22,8 @@ export class BedBuilder {
|
|
|
23
22
|
constructor(target, options) {
|
|
24
23
|
this.target = target;
|
|
25
24
|
// A supplied reader is a claim — verify it the way the container itself does.
|
|
26
|
-
this.reader = verifyingMetadataReader(options
|
|
27
|
-
|
|
28
|
-
this.mockFactory = options?.mockFactory ?? defaultMockFactory;
|
|
25
|
+
this.reader = verifyingMetadataReader(options.metadataReader ?? defaultMetadataReader);
|
|
26
|
+
this.mockFactory = options.mockFactory;
|
|
29
27
|
}
|
|
30
28
|
/**
|
|
31
29
|
* Replaces the auto-mock for one dependency with a hand-written stub or a concrete value.
|
|
@@ -61,13 +61,14 @@ export class SociableBuilder extends BedBuilder {
|
|
|
61
61
|
container.bind(realToken).to(realClass).singleton();
|
|
62
62
|
container.bind(realClass).toDynamic((context) => context.resolve(realToken));
|
|
63
63
|
for (const slot of slots) {
|
|
64
|
-
const builder = container.bind(realClass)
|
|
64
|
+
const builder = container.bind(realClass);
|
|
65
65
|
if (slot.name !== undefined) {
|
|
66
66
|
builder.whenNamed(slot.name);
|
|
67
67
|
}
|
|
68
68
|
for (const tag of slot.tags ?? []) {
|
|
69
69
|
builder.whenTagged(tag);
|
|
70
70
|
}
|
|
71
|
+
builder.toDynamic((context) => context.resolve(realToken));
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
// Sealed entries so mocks.get points at bed.exposed() instead of handing back a fake Mocked.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The entry
|
|
1
|
+
/** The entry points for building isolated units under test. */
|
|
2
2
|
import type { Constructor } from "@codefast/di";
|
|
3
3
|
import type { MockFunction } from "#mocking/mock-factory";
|
|
4
4
|
import type { Spy } from "#mocking/spy";
|
|
@@ -6,33 +6,43 @@ import type { TestBedOptions } from "#test-bed/bed-builder";
|
|
|
6
6
|
import type { SociableTestBedBuilder } from "#test-bed/sociable-builder";
|
|
7
7
|
import type { SolitaryTestBedBuilder } from "#test-bed/solitary-builder";
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Begins test beds for classes under test, every one of them mocked with the same backend.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam Backend - The spy type every bed's mocks are built with, fixed when the entry point is created.
|
|
10
12
|
*
|
|
11
13
|
* @since 0.1.0
|
|
12
14
|
*/
|
|
13
|
-
export interface TestBedStatic {
|
|
15
|
+
export interface TestBedStatic<Backend extends MockFunction> {
|
|
14
16
|
/**
|
|
15
17
|
* Begins a solitary test bed for `target`, auto-mocking every dependency it declares.
|
|
16
|
-
*
|
|
17
|
-
* @remarks The mock factory's return type becomes `Backend` and types every mock the bed hands
|
|
18
|
-
* out — pass `{ mockFactory: () => vi.fn() }` and `mocks.get(X).method` carries Vitest's own
|
|
19
|
-
* mock surface.
|
|
20
18
|
*/
|
|
21
|
-
solitary<Class
|
|
19
|
+
solitary<Class>(target: Constructor<Class>): SolitaryTestBedBuilder<Class, Backend>;
|
|
22
20
|
/**
|
|
23
21
|
* Begins a sociable test bed for `target`: chosen class collaborators stay real, tokens stay mocked.
|
|
24
22
|
*
|
|
25
23
|
* @remarks Returns only `expose` — a sociable bed without at least one exposed collaborator is a
|
|
26
24
|
* solitary bed, so the type steers the first call.
|
|
27
25
|
*/
|
|
28
|
-
sociable<Class
|
|
26
|
+
sociable<Class>(target: Constructor<Class>): Pick<SociableTestBedBuilder<Class, Backend>, "expose">;
|
|
29
27
|
}
|
|
30
28
|
/**
|
|
31
|
-
*
|
|
29
|
+
* Creates the test-bed entry point for one mock backend, so a suite states its backend once.
|
|
30
|
+
*
|
|
31
|
+
* @remarks The factory's return type becomes `Backend` and types every mock the beds hand out —
|
|
32
|
+
* `createTestBed({ mockFactory: () => vi.fn() })` gives `mocks.get(X).method` Vitest's own mock
|
|
33
|
+
* surface. A bed can have no other backend than the entry point it was begun from.
|
|
34
|
+
*
|
|
35
|
+
* @throws MissingMockFactoryError When no factory is passed, which only a caller past the types can do.
|
|
36
|
+
*
|
|
37
|
+
* @since 0.2.0
|
|
38
|
+
*/
|
|
39
|
+
export declare function createTestBed<Backend extends MockFunction>(options: TestBedOptions<Backend>): TestBedStatic<Backend>;
|
|
40
|
+
/**
|
|
41
|
+
* The test-bed entry point on the built-in spy, the backend with no test-framework dependency.
|
|
32
42
|
*
|
|
33
|
-
* @remarks `solitary` and `sociable` record the target
|
|
34
|
-
*
|
|
43
|
+
* @remarks `solitary` and `sociable` record the target only; nothing is instantiated until
|
|
44
|
+
* `compile()`. A suite on another backend creates its own with `createTestBed`.
|
|
35
45
|
*
|
|
36
46
|
* @since 0.1.0
|
|
37
47
|
*/
|
|
38
|
-
export declare const TestBed: TestBedStatic
|
|
48
|
+
export declare const TestBed: TestBedStatic<Spy>;
|
|
@@ -1,19 +1,37 @@
|
|
|
1
|
-
/** The entry
|
|
1
|
+
/** The entry points for building isolated units under test. */
|
|
2
|
+
import { MissingMockFactoryError } from "#errors/errors";
|
|
3
|
+
import { defaultMockFactory } from "#mocking/mock-factory";
|
|
2
4
|
import { SociableBuilder } from "#test-bed/sociable-builder";
|
|
3
5
|
import { SolitaryBuilder } from "#test-bed/solitary-builder";
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* Creates the test-bed entry point for one mock backend, so a suite states its backend once.
|
|
6
8
|
*
|
|
7
|
-
* @remarks
|
|
8
|
-
*
|
|
9
|
+
* @remarks The factory's return type becomes `Backend` and types every mock the beds hand out —
|
|
10
|
+
* `createTestBed({ mockFactory: () => vi.fn() })` gives `mocks.get(X).method` Vitest's own mock
|
|
11
|
+
* surface. A bed can have no other backend than the entry point it was begun from.
|
|
12
|
+
*
|
|
13
|
+
* @throws MissingMockFactoryError When no factory is passed, which only a caller past the types can do.
|
|
14
|
+
*
|
|
15
|
+
* @since 0.2.0
|
|
16
|
+
*/
|
|
17
|
+
export function createTestBed(options) {
|
|
18
|
+
// Read once, so a later write to the caller's object cannot change the beds already begun.
|
|
19
|
+
const { mockFactory, metadataReader } = options;
|
|
20
|
+
if (typeof mockFactory !== "function") {
|
|
21
|
+
throw new MissingMockFactoryError();
|
|
22
|
+
}
|
|
23
|
+
const resolved = { mockFactory, metadataReader };
|
|
24
|
+
return {
|
|
25
|
+
solitary: (target) => new SolitaryBuilder(target, resolved),
|
|
26
|
+
sociable: (target) => new SociableBuilder(target, resolved),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The test-bed entry point on the built-in spy, the backend with no test-framework dependency.
|
|
31
|
+
*
|
|
32
|
+
* @remarks `solitary` and `sociable` record the target only; nothing is instantiated until
|
|
33
|
+
* `compile()`. A suite on another backend creates its own with `createTestBed`.
|
|
9
34
|
*
|
|
10
35
|
* @since 0.1.0
|
|
11
36
|
*/
|
|
12
|
-
export const TestBed = {
|
|
13
|
-
solitary(target, options) {
|
|
14
|
-
return new SolitaryBuilder(target, options);
|
|
15
|
-
},
|
|
16
|
-
sociable(target, options) {
|
|
17
|
-
return new SociableBuilder(target, options);
|
|
18
|
-
},
|
|
19
|
-
};
|
|
37
|
+
export const TestBed = createTestBed({ mockFactory: defaultMockFactory });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/** The compiled result of a test bed: the real unit plus handles to its mocks. */
|
|
2
2
|
import { tokenName } from "@codefast/di";
|
|
3
3
|
import { criteriaEquals, normalizeCriteria } from "#discovery/mock-binder";
|
|
4
4
|
import { ExposureError, SealedDependencyError, UndeclaredDependencyError } from "#errors/errors";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codefast/di-testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Solitary and sociable auto-mocking test beds for @codefast/di",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auto-mock",
|
|
@@ -100,9 +100,9 @@
|
|
|
100
100
|
"access": "public"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
103
|
-
"@codefast/di": ">=0.
|
|
103
|
+
"@codefast/di": ">=0.12.0"
|
|
104
104
|
},
|
|
105
105
|
"engines": {
|
|
106
|
-
"node": ">=
|
|
106
|
+
"node": ">=24.0.0"
|
|
107
107
|
}
|
|
108
108
|
}
|