@codefast/di-testing 0.1.3 → 0.2.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 CHANGED
@@ -1,5 +1,83 @@
1
1
  # @codefast/di-testing
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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
8
+ another. `createTestBed({ mockFactory, metadataReader? })` returns the entry point every bed begins from, its `Backend`
9
+ inferred from the factory; `TestBed.solitary(target)` and `TestBed.sociable(target)` take the target only. The `TestBed`
10
+ export is `createTestBed({ mockFactory: defaultMockFactory })`, the built-in spy with no framework needed.
11
+
12
+ Before, the backend was chosen per call — `TestBed.solitary(Unit, { mockFactory: () => vi.fn() })` — and
13
+ `TestBed.solitary<Unit, SinonStub>(Unit)` with no factory type-checked, typed every mock as `SinonStub`, and built the
14
+ default spy. Migrate by creating the suite's entry point once:
15
+
16
+ ```ts
17
+ import { createTestBed } from "@codefast/di-testing";
18
+ import { vi } from "vitest";
19
+
20
+ export const TestBed = createTestBed({ mockFactory: () => vi.fn() });
21
+ ```
22
+
23
+ `TestBedOptions<Backend>` configures `createTestBed` and requires `mockFactory`; `TestBedStatic<Backend>` names the
24
+ entry point's backend and has no default. `createTestBed` throws the new `MissingMockFactoryError`
25
+ (`MISSING_MOCK_FACTORY`) for a caller past the types who passes no factory, instead of standing the built-in spy in for
26
+ a backend nobody chose.
27
+
28
+ - [#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
29
+ release with explicit resource management built in (`using`, `await using`, `DisposableStack`, `AsyncDisposableStack`,
30
+ `SuppressedError`) and all of ES2025, so the packages use both as the platform ships them instead of shimming them for
31
+ 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
32
+ while a deployment still runs Node 22.
33
+
34
+ ### Patch Changes
35
+
36
+ - [#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,
37
+ which no numbered `lib` declares before ES2027. `@types/node` 24 or later loads them, and so does `ESNext.Disposable` in
38
+ `lib`. Without either, TypeScript 7 fails inside `container.d.ts` with TS2550 under `skipLibCheck: false`, and at
39
+ `await using` with TS2318. `@codefast/di-testing` drops a `/// <reference lib="esnext.disposable" />` that TypeScript 7
40
+ stripped from its emitted declarations anyway, and takes the lib from its `tsconfig.json` instead.
41
+
42
+ - [#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
43
+ moves that range, so the manifest is the one place it is stated.
44
+
45
+ - [#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/*`
46
+ package is built and checked with. No code or declaration changed.
47
+ - Updated dependencies:
48
+ - @codefast/di@0.11.0
49
+
50
+ ## 0.1.4
51
+
52
+ ### Patch Changes
53
+
54
+ - [#892](https://github.com/codefastlabs/codefast/pull/892) [`4c97c5b`](https://github.com/codefastlabs/codefast/commit/4c97c5b8c2314f25ee9df7eac5a4b5dd723d6d90) Thanks [@thevuong](https://github.com/thevuong)! - Lower the monorepo's Node floor from 24 to 22.12, so the packages install and run on the active Node 22 LTS line.
55
+
56
+ `engines.node` becomes `>=22.12.0` across every package — the floor the shared toolchain (oxlint, Vite, Vitest, TanStack
57
+ Start) already requires. Development stays on the latest Node (`.node-version`) for speed, and a CI matrix exercises the
58
+ floor and the active LTS directly, so the floor is a contract CI proves rather than one everyone has to run.
59
+ `@types/node` is pinned to the floor's major (`^22`), with a workspace override holding the whole tree there so a dev
60
+ tool's `@types/node: "*"` peer can no longer pull a newer major and mask an API the floor lacks. The floor stays
61
+ mechanical, not advisory: `@codefast/di` keeps its own `Map` upsert helpers rather than the ES2025
62
+ `Map.prototype.getOrInsert` (which would raise the floor to 26) and its `lib` stays `ES2024`. `@codefast/cli`'s mirror
63
+ step now calls the `node:path` functions directly instead of aliasing them, which the floor's types correctly flag as
64
+ unbound methods.
65
+
66
+ The shared `@codefast/typescript-config` presets pin `lib` and `target` to `ES2024` (was `ESNext`) so the compiler's
67
+ ECMAScript surface matches the Node floor: an ES2025 builtin such as `Map.prototype.getOrInsert` now fails to type-check
68
+ rather than compiling and crashing on Node 22.12. `@codefast/di` and `@codefast/di-testing` already pinned `lib` and are
69
+ unchanged.
70
+
71
+ Internal subpath imports move from a `#/` prefix to a bare `#` (`#core/token`, not `#/core/token`), and the
72
+ `package.json#imports` keys become `#*`/`#tests/*`/`#examples/*` to match. Node's native ESM resolver rejects a
73
+ `#/`-prefixed specifier with `ERR_INVALID_MODULE_SPECIFIER` on the whole Node 22 line (and on Node 24 before 24.14), and
74
+ each package ships those specifiers verbatim inside its published `dist/*.js` for a consumer's Node to resolve — so this
75
+ rename is what actually lets the packages import on the new floor. Purely internal: a consumer's own import paths are
76
+ unchanged.
77
+
78
+ - Updated dependencies [[`4c97c5b`](https://github.com/codefastlabs/codefast/commit/4c97c5b8c2314f25ee9df7eac5a4b5dd723d6d90)]:
79
+ - @codefast/di@0.10.1
80
+
3
81
  ## 0.1.3
4
82
 
5
83
  ### 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 mock is a small built-in spy. Pass `() => vi.fn()` — or `jest.fn`,
23
- `() => sinon.stub()` — to build the mocks from that backend and use its matchers instead.
24
- - **Backend-typed lookups.** The factory's return type flows through the whole bed: with `() => vi.fn()`,
25
- `mocks.get(EmailToken).send` carries Vitest's own mock surface (`mockReturnValueOnce`, `mockClear`, and so on), with
26
- no adapter package and no module augmentation.
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 24 or later and a peer install of `@codefast/di` (`>=0.8.0`), with the same
35
- TypeScript setup: native Stage 3 decorators, `experimentalDecorators` off. The package is published on 0.x and versioned
36
- on its own track: breaking changes ship as minor versions, so pin the minor version when you need stability.
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 { TestBed } from "@codefast/di-testing";
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, { mockFactory: () => vi.fn() })
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 reads the same, minus the `mockFactory`. Assert against the built-in spy's `.mock.calls` and
89
- stub with `.mockReturnValue()`:
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
- ## Solitary beds
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
- `TestBed.solitary(target, options?)` begins a bed for `target` and auto-mocks every dependency it declares. Nothing is
103
- instantiated until you call `compile()`. The options:
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?: () => spy` — the spy backend each auto-mock is built from. Defaults to the built-in spy.
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, options?)` takes the same options and returns only
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 { TestBed } from "@codefast/di-testing";
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, { mockFactory: () => vi.fn() })
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; that needs the `esnext.disposable` lib in your TypeScript configuration if your `target` does not include it.
233
+ block.
210
234
 
211
235
  The lower-level pieces are exported too: `createAutoMock`, `createSpy`, `defaultMockFactory`, and the `Mocked`,
212
- `DeepPartial`, `MockFactory`, and `Spy` types.
236
+ `DeepPartial`, `MockFactory`, `Spy`, and `TestBedOptions` types.
213
237
 
214
238
  ## Errors
215
239
 
@@ -1,5 +1,5 @@
1
1
  /** Reads the dependencies a class declares, the input every auto-mock is built from. */
2
- import { NotInjectableError } from "#/errors/errors";
2
+ import { NotInjectableError } from "#errors/errors";
3
3
  /**
4
4
  * Reads every dependency a class declares — constructor parameters first, then accessor injections.
5
5
  *
@@ -1,6 +1,6 @@
1
1
  /** Binds a unit's discovered dependencies onto a container as mocks — the sole container coupling. */
2
2
  import type { BindingTag, Constructor, Container, DependencyKey, DependencySlot, InjectOptions } from "@codefast/di";
3
- import type { MockFactory } from "#/mocking/mock-factory";
3
+ import type { MockFactory } from "#mocking/mock-factory";
4
4
  /**
5
5
  * How one dependency is supplied instead of a plain auto-mock.
6
6
  *
@@ -1,7 +1,7 @@
1
1
  /** Binds a unit's discovered dependencies onto a container as mocks — the sole container coupling. */
2
2
  import { slotName, tokenName } from "@codefast/di";
3
- import { OverrideMismatchError, UndeclaredDependencyError } from "#/errors/errors";
4
- import { createAutoMock } from "#/mocking/auto-mock";
3
+ import { OverrideMismatchError, UndeclaredDependencyError } from "#errors/errors";
4
+ import { createAutoMock } from "#mocking/auto-mock";
5
5
  /** The name prefix `usingAll` elements are bound under, so identical constants keep distinct slots. */
6
6
  const ALL_ELEMENT_SLOT_PREFIX = "di-testing:all:";
7
7
  /**
@@ -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
  *
@@ -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,16 +1,16 @@
1
1
  /** Solitary and sociable auto-mocking test beds for `@codefast/di`. */
2
- export { TestBed } from "#/test-bed/test-bed";
3
- export type { TestBedStatic } from "#/test-bed/test-bed";
4
- export type { MockOverrideBuilder, PreparedBed, TestBedOptions } from "#/test-bed/bed-builder";
5
- export type { SolitaryTestBedBuilder } from "#/test-bed/solitary-builder";
6
- export type { SociableTestBedBuilder } from "#/test-bed/sociable-builder";
7
- export type { SociableUnitTestBed, UnitReference, UnitTestBed } from "#/test-bed/unit-test-bed";
8
- export { createAutoMock, MOCK_RESET } from "#/mocking/auto-mock";
9
- export type { DeepPartial, Mocked } from "#/mocking/auto-mock";
10
- export { defaultMockFactory } from "#/mocking/mock-factory";
11
- export type { MockFactory, MockFunction } from "#/mocking/mock-factory";
12
- export { createSpy } from "#/mocking/spy";
13
- export type { Spy, SpyResult, SpyState } from "#/mocking/spy";
14
- export type { InjectionIdentifier } from "#/types";
15
- export { ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#/errors/errors";
16
- export type { SealedCause } from "#/errors/errors";
2
+ export { createTestBed, TestBed } from "#test-bed/test-bed";
3
+ export type { TestBedStatic } from "#test-bed/test-bed";
4
+ export type { MockOverrideBuilder, PreparedBed, TestBedOptions } from "#test-bed/bed-builder";
5
+ export type { SolitaryTestBedBuilder } from "#test-bed/solitary-builder";
6
+ export type { SociableTestBedBuilder } from "#test-bed/sociable-builder";
7
+ export type { SociableUnitTestBed, UnitReference, UnitTestBed } from "#test-bed/unit-test-bed";
8
+ export { createAutoMock, MOCK_RESET } from "#mocking/auto-mock";
9
+ export type { DeepPartial, Mocked } from "#mocking/auto-mock";
10
+ export { defaultMockFactory } from "#mocking/mock-factory";
11
+ export type { MockFactory, MockFunction } from "#mocking/mock-factory";
12
+ export { createSpy } from "#mocking/spy";
13
+ export type { Spy, SpyResult, SpyState } from "#mocking/spy";
14
+ export type { InjectionIdentifier } from "#types";
15
+ export { MissingMockFactoryError, ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#errors/errors";
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";
3
- export { createAutoMock, MOCK_RESET } from "#/mocking/auto-mock";
4
- export { defaultMockFactory } from "#/mocking/mock-factory";
5
- export { createSpy } from "#/mocking/spy";
6
- export { ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#/errors/errors";
2
+ export { createTestBed, TestBed } from "#test-bed/test-bed";
3
+ export { createAutoMock, MOCK_RESET } from "#mocking/auto-mock";
4
+ export { defaultMockFactory } from "#mocking/mock-factory";
5
+ export { createSpy } from "#mocking/spy";
6
+ export { MissingMockFactoryError, ExposureError, NotInjectableError, OverrideMismatchError, SealedDependencyError, TestingError, UndeclaredDependencyError, } from "#errors/errors";
@@ -1,6 +1,6 @@
1
1
  /** The lazy `Proxy` that mocks an erased interface one accessed property at a time. */
2
- import type { MockFactory, MockFunction } from "#/mocking/mock-factory";
3
- import type { Spy } from "#/mocking/spy";
2
+ import type { MockFactory, MockFunction } from "#mocking/mock-factory";
3
+ import type { Spy } from "#mocking/spy";
4
4
  /**
5
5
  * A mocked view of `Dependency`: every member becomes a spy, nested objects are mocked in turn.
6
6
  *
@@ -1,5 +1,5 @@
1
1
  /** The pluggable seam that decides which spy backend the auto-mocks are built from. */
2
- import type { Spy } from "#/mocking/spy";
2
+ import type { Spy } from "#mocking/spy";
3
3
  /**
4
4
  * The loose callable an auto-mock materializes for each accessed property.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /** The pluggable seam that decides which spy backend the auto-mocks are built from. */
2
- import { createSpy } from "#/mocking/spy";
2
+ import { createSpy } from "#mocking/spy";
3
3
  /**
4
4
  * The default `MockFactory` — one built-in {@link Spy} per call, with no test-framework dependency.
5
5
  *
@@ -1,21 +1,21 @@
1
1
  /** The override-recording core both test-bed builders extend. */
2
2
  import type { Constructor, Container, DependencyKey, InjectOptions, MetadataReader } from "@codefast/di";
3
- import type { BoundMock, SlottedOverride } from "#/discovery/mock-binder";
4
- import type { DeepPartial } from "#/mocking/auto-mock";
5
- import type { MockFactory, MockFunction } from "#/mocking/mock-factory";
6
- import type { Spy } from "#/mocking/spy";
7
- import type { InjectionIdentifier } from "#/types";
3
+ import type { BoundMock, SlottedOverride } from "#discovery/mock-binder";
4
+ import type { DeepPartial } from "#mocking/auto-mock";
5
+ import type { MockFactory, MockFunction } from "#mocking/mock-factory";
6
+ import type { Spy } from "#mocking/spy";
7
+ import type { InjectionIdentifier } from "#types";
8
8
  /**
9
- * Options that configure a whole test-bed compile.
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 = Spy> {
17
- /** Spy factory each auto-mock property is materialized with; defaults to the built-in spy. */
18
- readonly mockFactory?: MockFactory<Backend> | undefined;
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?: TestBedOptions<Backend>);
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
3
  import { verifyingMetadataReader } from "@codefast/di/metadata/verifying-metadata-reader";
4
- import { criteriaEquals, normalizeCriteria } from "#/discovery/mock-binder";
5
- import { defaultMockFactory } from "#/mocking/mock-factory";
4
+ import { criteriaEquals, normalizeCriteria } from "#discovery/mock-binder";
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?.metadataReader ?? defaultMetadataReader);
27
- // With no factory the caller's Backend defaulted to Spy, which is what the default produces.
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.
@@ -1,11 +1,11 @@
1
1
  /** The fluent builder that compiles a unit together with chosen real collaborators. */
2
2
  import type { Constructor, InjectOptions } from "@codefast/di";
3
- import type { MockFunction } from "#/mocking/mock-factory";
4
- import type { Spy } from "#/mocking/spy";
5
- import type { MockOverrideBuilder } from "#/test-bed/bed-builder";
6
- import { BedBuilder } from "#/test-bed/bed-builder";
7
- import type { SociableUnitTestBed } from "#/test-bed/unit-test-bed";
8
- import type { InjectionIdentifier } from "#/types";
3
+ import type { MockFunction } from "#mocking/mock-factory";
4
+ import type { Spy } from "#mocking/spy";
5
+ import type { MockOverrideBuilder } from "#test-bed/bed-builder";
6
+ import { BedBuilder } from "#test-bed/bed-builder";
7
+ import type { SociableUnitTestBed } from "#test-bed/unit-test-bed";
8
+ import type { InjectionIdentifier } from "#types";
9
9
  /**
10
10
  * A sociable build in progress: expose real collaborators, override the rest, then compile.
11
11
  *
@@ -1,10 +1,10 @@
1
1
  /** The fluent builder that compiles a unit together with chosen real collaborators. */
2
2
  import { Container, token } from "@codefast/di";
3
- import { scanSociableDependencies } from "#/discovery/dependency-scanner";
4
- import { bindMocks } from "#/discovery/mock-binder";
5
- import { ExposureError } from "#/errors/errors";
6
- import { BedBuilder } from "#/test-bed/bed-builder";
7
- import { createSociableUnitTestBed, createUnitTestBed } from "#/test-bed/unit-test-bed";
3
+ import { scanSociableDependencies } from "#discovery/dependency-scanner";
4
+ import { bindMocks } from "#discovery/mock-binder";
5
+ import { ExposureError } from "#errors/errors";
6
+ import { BedBuilder } from "#test-bed/bed-builder";
7
+ import { createSociableUnitTestBed, createUnitTestBed } from "#test-bed/unit-test-bed";
8
8
  /**
9
9
  * The default {@link SociableTestBedBuilder}, backed by a fresh container per compile.
10
10
  *
@@ -1,11 +1,11 @@
1
1
  /** The fluent builder that compiles a solitary unit under test. */
2
2
  import type { InjectOptions } from "@codefast/di";
3
- import type { MockFunction } from "#/mocking/mock-factory";
4
- import type { Spy } from "#/mocking/spy";
5
- import type { MockOverrideBuilder } from "#/test-bed/bed-builder";
6
- import { BedBuilder } from "#/test-bed/bed-builder";
7
- import type { UnitTestBed } from "#/test-bed/unit-test-bed";
8
- import type { InjectionIdentifier } from "#/types";
3
+ import type { MockFunction } from "#mocking/mock-factory";
4
+ import type { Spy } from "#mocking/spy";
5
+ import type { MockOverrideBuilder } from "#test-bed/bed-builder";
6
+ import { BedBuilder } from "#test-bed/bed-builder";
7
+ import type { UnitTestBed } from "#test-bed/unit-test-bed";
8
+ import type { InjectionIdentifier } from "#types";
9
9
  /**
10
10
  * A solitary build in progress: register overrides, then compile.
11
11
  *
@@ -1,9 +1,9 @@
1
1
  /** The fluent builder that compiles a solitary unit under test. */
2
2
  import { Container } from "@codefast/di";
3
- import { scanDependencies } from "#/discovery/dependency-scanner";
4
- import { bindMocks } from "#/discovery/mock-binder";
5
- import { BedBuilder } from "#/test-bed/bed-builder";
6
- import { createUnitTestBed } from "#/test-bed/unit-test-bed";
3
+ import { scanDependencies } from "#discovery/dependency-scanner";
4
+ import { bindMocks } from "#discovery/mock-binder";
5
+ import { BedBuilder } from "#test-bed/bed-builder";
6
+ import { createUnitTestBed } from "#test-bed/unit-test-bed";
7
7
  /**
8
8
  * The default {@link SolitaryTestBedBuilder}, backed by a fresh container per compile.
9
9
  *
@@ -1,38 +1,48 @@
1
- /** The entry point for building isolated units under test. */
1
+ /** The entry points for building isolated units under test. */
2
2
  import type { Constructor } from "@codefast/di";
3
- import type { MockFunction } from "#/mocking/mock-factory";
4
- import type { Spy } from "#/mocking/spy";
5
- import type { TestBedOptions } from "#/test-bed/bed-builder";
6
- import type { SociableTestBedBuilder } from "#/test-bed/sociable-builder";
7
- import type { SolitaryTestBedBuilder } from "#/test-bed/solitary-builder";
3
+ import type { MockFunction } from "#mocking/mock-factory";
4
+ import type { Spy } from "#mocking/spy";
5
+ import type { TestBedOptions } from "#test-bed/bed-builder";
6
+ import type { SociableTestBedBuilder } from "#test-bed/sociable-builder";
7
+ import type { SolitaryTestBedBuilder } from "#test-bed/solitary-builder";
8
8
  /**
9
- * The factory surface for building isolated units under test.
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, Backend extends MockFunction = Spy>(target: Constructor<Class>, options?: TestBedOptions<Backend>): SolitaryTestBedBuilder<Class, Backend>;
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, Backend extends MockFunction = Spy>(target: Constructor<Class>, options?: TestBedOptions<Backend>): Pick<SociableTestBedBuilder<Class, Backend>, "expose">;
26
+ sociable<Class>(target: Constructor<Class>): Pick<SociableTestBedBuilder<Class, Backend>, "expose">;
29
27
  }
30
28
  /**
31
- * Entry point for auto-mocking a class in isolation from its collaborators.
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 and options only; nothing is instantiated
34
- * until `compile()`.
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 point for building isolated units under test. */
2
- import { SociableBuilder } from "#/test-bed/sociable-builder";
3
- import { SolitaryBuilder } from "#/test-bed/solitary-builder";
1
+ /** The entry points for building isolated units under test. */
2
+ import { MissingMockFactoryError } from "#errors/errors";
3
+ import { defaultMockFactory } from "#mocking/mock-factory";
4
+ import { SociableBuilder } from "#test-bed/sociable-builder";
5
+ import { SolitaryBuilder } from "#test-bed/solitary-builder";
4
6
  /**
5
- * Entry point for auto-mocking a class in isolation from its collaborators.
7
+ * Creates the test-bed entry point for one mock backend, so a suite states its backend once.
6
8
  *
7
- * @remarks `solitary` and `sociable` record the target and options only; nothing is instantiated
8
- * until `compile()`.
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,9 +1,9 @@
1
1
  /** The compiled result of a test bed: the real unit plus handles to its mocks. */
2
2
  import type { Constructor, Container, DependencyKey, InjectOptions, TokenValue } from "@codefast/di";
3
- import type { BoundMock } from "#/discovery/mock-binder";
4
- import type { Mocked } from "#/mocking/auto-mock";
5
- import type { MockFunction } from "#/mocking/mock-factory";
6
- import type { Spy } from "#/mocking/spy";
3
+ import type { BoundMock } from "#discovery/mock-binder";
4
+ import type { Mocked } from "#mocking/auto-mock";
5
+ import type { MockFunction } from "#mocking/mock-factory";
6
+ import type { Spy } from "#mocking/spy";
7
7
  /**
8
8
  * A lookup from a dependency's token or class to the mock the unit was built with.
9
9
  *
@@ -1,8 +1,8 @@
1
- /// <reference lib="esnext.disposable" />
1
+ /** The compiled result of a test bed: the real unit plus handles to its mocks. */
2
2
  import { tokenName } from "@codefast/di";
3
- import { criteriaEquals, normalizeCriteria } from "#/discovery/mock-binder";
4
- import { ExposureError, SealedDependencyError, UndeclaredDependencyError } from "#/errors/errors";
5
- import { MOCK_RESET } from "#/mocking/auto-mock";
3
+ import { criteriaEquals, normalizeCriteria } from "#discovery/mock-binder";
4
+ import { ExposureError, SealedDependencyError, UndeclaredDependencyError } from "#errors/errors";
5
+ import { MOCK_RESET } from "#mocking/auto-mock";
6
6
  /**
7
7
  * Wraps a compiled bed with access to its exposed real collaborators.
8
8
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codefast/di-testing",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Solitary and sociable auto-mocking test beds for @codefast/di",
5
5
  "keywords": [
6
6
  "auto-mock",
@@ -36,7 +36,7 @@
36
36
  "module": "./dist/index.js",
37
37
  "types": "./dist/index.d.ts",
38
38
  "imports": {
39
- "#/*": {
39
+ "#*": {
40
40
  "types": "./dist/*.d.ts",
41
41
  "default": "./dist/*.js"
42
42
  }
@@ -100,7 +100,7 @@
100
100
  "access": "public"
101
101
  },
102
102
  "peerDependencies": {
103
- "@codefast/di": ">=0.9.0"
103
+ "@codefast/di": ">=0.11.0"
104
104
  },
105
105
  "engines": {
106
106
  "node": ">=24.0.0"