vitest-auto-spy 4.0.1 → 4.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/AGENTS.md +104 -19
- package/README.md +262 -53
- package/dist/angular.js +421 -31
- package/dist/bun-angular.js +5 -5
- package/dist/bun.js +3 -3
- package/dist/{chunk-XJ3UMJN3.js → chunk-2ZG27FVL.js} +1 -1
- package/dist/{chunk-N4PT3JYE.js → chunk-4KCYFAJX.js} +1 -1
- package/dist/{chunk-3QJUFL2N.js → chunk-4KK3ES4P.js} +49 -8
- package/dist/{chunk-M3XZL5NF.js → chunk-5URFSYDT.js} +2 -2
- package/dist/{chunk-6EGUFU73.js → chunk-KEKKEIRG.js} +1 -1
- package/dist/chunk-WQZ2RKS7.js +397 -0
- package/dist/chunk-YHDBRBGU.js +5 -0
- package/dist/cli.js +546 -9
- package/dist/console.js +4 -2
- package/dist/dom-stubs.js +378 -7
- package/dist/index.js +421 -31
- package/dist/jasmine.js +5 -4
- package/dist/nestjs.js +6 -5
- package/dist/node.cjs +48 -7
- package/dist/node.js +3 -3
- package/dist/perf-reporter.js +52 -0
- package/dist/react.js +5 -4
- package/dist/rxjs.js +1 -3
- package/dist/setup.d.ts +19 -1
- package/dist/setup.js +11 -3
- package/dist/svelte.js +5 -4
- package/dist/vue.js +6 -5
- package/package.json +19 -4
- package/skills/vitest-auto-spy/SKILL.md +67 -61
- package/dist/chunk-MG5EAMZR.js +0 -36
package/AGENTS.md
CHANGED
|
@@ -261,19 +261,23 @@ xhr.send.mockImplementation(() => respond(asInstance(xhr)));
|
|
|
261
261
|
|
|
262
262
|
### Cost, so it stops being a question
|
|
263
263
|
|
|
264
|
-
Building a spy is not a thing to optimise. On a ten-method class
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
`
|
|
269
|
-
|
|
264
|
+
Building a spy is not a thing to optimise. On a ten-method class, `npm run bench` on Node v24.19.0
|
|
265
|
+
and Vitest 4.1.11, measured 2026-09-04: `createSpyFromClass` plus two methods called **2.25 µs**,
|
|
266
|
+
plus all ten 5.83 µs, `createAutoMock` plus four members 1.79 µs, a `calledWith` lookup 0.21 µs —
|
|
267
|
+
five providers across two thousand tests is about two hundredths of a second. Call the factory in
|
|
268
|
+
`beforeEach` and look at `TestBed` instead. The only two settings that cost:
|
|
269
|
+
`{ lazySpies: false }` gives up the laziness `provideAutoSpy` defaults to, and
|
|
270
|
+
`autoSpyAccessors: true` walks the prototype chain uncached on every call — name the accessors
|
|
271
|
+
instead.
|
|
270
272
|
|
|
271
273
|
Memory is the exception, and only on classes wide enough for it to matter. `lazySpies: true` still
|
|
272
274
|
defines one accessor per method, and that placeholder is nearly all of what an untouched double
|
|
273
|
-
retains:
|
|
274
|
-
every method from a single trap object instead
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
retains: an untouched 100-method double holds 25 601 B against 4 097 B with `lazySpies: 'proxy'`,
|
|
276
|
+
which answers every method from a single trap object instead — 256 B per method against 41 B
|
|
277
|
+
(`npm run bench:memory`, 2026-09-04). It is opt-in because a `Proxy` cannot remove itself: the
|
|
278
|
+
default leaves a plain data property behind once a method materialises, while the proxy goes through
|
|
279
|
+
a trap on every read and every call for the life of the double, and on a narrow class it is also
|
|
280
|
+
slower to create. Reach for it on generated API clients and ngrx facades under `isolate: false`,
|
|
277
281
|
where the doubles of a whole file are alive at once; leave the default everywhere else.
|
|
278
282
|
|
|
279
283
|
---
|
|
@@ -1053,6 +1057,48 @@ is therefore remembered and put back, in `beforeEach`, but only when it has gone
|
|
|
1053
1057
|
returns how many went), `restoreLongLivedImplementations()` is that repair (it returns how many it
|
|
1054
1058
|
put back) and `getMockRegistrySize()` reports what is left.
|
|
1055
1059
|
|
|
1060
|
+
One entry is never pruned, and it is not one of yours: the Vitest adapter registers a single
|
|
1061
|
+
`vi.fn()` whose `mockClear` sweeps this library's own spies. Dropping it would turn
|
|
1062
|
+
`vi.clearAllMocks()` into a silent no-op for every double in the run, so it carries a mark the
|
|
1063
|
+
pruner skips. Why the library needs a mock of its own for that is the next section.
|
|
1064
|
+
|
|
1065
|
+
### What a method spy is, and the one thing that differs from `vi.fn()`
|
|
1066
|
+
|
|
1067
|
+
A method spy is **not** a `vi.fn()`. It is this library's own mock function: one shared prototype
|
|
1068
|
+
carrying the whole `Mock` surface, call state allocated on the first call rather than at creation,
|
|
1069
|
+
and no entry in any global registry. `vi.fn()` assigns some twenty-five closures as own properties
|
|
1070
|
+
of every mock it makes and allocates six arrays up front — per method, on every double a spec
|
|
1071
|
+
builds — and that cost is most of what a wide service used to pay for methods no test touches.
|
|
1072
|
+
|
|
1073
|
+
Everything a spec can observe is the same, and the suite pins it by putting the two side by side:
|
|
1074
|
+
|
|
1075
|
+
- `vi.isMockFunction(spy.load)`, `expect(spy.load).toHaveBeenCalledWith(…)`, `toHaveReturned`,
|
|
1076
|
+
`toHaveResolved`, `toHaveBeenNthCalledWith` — every matcher reads `_isMockFunction`, `mock.*` and
|
|
1077
|
+
`getMockName()`, all of which the spy implements with the runner's semantics.
|
|
1078
|
+
- `spy.load.mock.calls` / `.results` / `.settledResults` / `.instances` / `.contexts` /
|
|
1079
|
+
`.invocationCallOrder` / `.lastCall`, `mockReturnValue`, `mockResolvedValue`,
|
|
1080
|
+
`mockImplementation(Once)`, `withImplementation`, `mockClear` / `mockReset` / `mockRestore`,
|
|
1081
|
+
`mockName`, `using`.
|
|
1082
|
+
- `vi.clearAllMocks()`, `vi.resetAllMocks()`, and the `clearMocks` / `mockReset` config keys, which
|
|
1083
|
+
Vitest applies through those two functions.
|
|
1084
|
+
|
|
1085
|
+
**The one difference.** `mock.invocationCallOrder` counts on this library's own scale, so
|
|
1086
|
+
`expect(a).toHaveBeenCalledBefore(b)` is exact between two auto-spies and meaningless between an
|
|
1087
|
+
auto-spy and a hand-written `vi.fn()` — the two counters never met. Nothing else in the library or in
|
|
1088
|
+
Vitest reads that field. If a suite needs the comparison, switch the whole run back to the runner's
|
|
1089
|
+
factory:
|
|
1090
|
+
|
|
1091
|
+
```ts
|
|
1092
|
+
// vitest.setup.ts
|
|
1093
|
+
import { setSpyEngine } from 'vitest-auto-spy/setup';
|
|
1094
|
+
|
|
1095
|
+
setSpyEngine('runner'); // every double built afterwards is `vi.fn()` per method, as before 4.1
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
`getSpyEngine()` reports the current one, `'auto-spy'` is the default, and doubles already built keep
|
|
1099
|
+
the engine they were built with. The switch is Vitest-only: on Bun and `node:test` the runner's own
|
|
1100
|
+
matchers recognise only the runner's own mocks, so those adapters keep using them.
|
|
1101
|
+
|
|
1056
1102
|
Two more switches, both about the environment rather than the spies:
|
|
1057
1103
|
|
|
1058
1104
|
```ts
|
|
@@ -2058,20 +2104,30 @@ thing to check by hand after the swap: Suites' double answers every property nam
|
|
|
2058
2104
|
stubbed a since-renamed method was passing over nothing and will now fail. Full mapping:
|
|
2059
2105
|
<https://asdalexey.github.io/vitest-auto-spy/migrating-suites>.
|
|
2060
2106
|
|
|
2061
|
-
- **`renderShallow` has no single speed number, and quoting one is a bug.**
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
the
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
`
|
|
2069
|
-
|
|
2107
|
+
- **`renderShallow` has no single speed number, and quoting one is a bug.** This repository commits
|
|
2108
|
+
no harness for the helper, so no surface publishes a ratio for it and none may be invented.
|
|
2109
|
+
Describe the shape instead: `renderShallow` is flat in the number of children because it never
|
|
2110
|
+
builds the subtree, while `TestBed.createComponent` scales linearly with it — so the win is
|
|
2111
|
+
however much markup the component under test happens to own, nothing on a leaf component and a
|
|
2112
|
+
great deal on a table or a dashboard. A per-render ratio would in any case be the upper bound on
|
|
2113
|
+
what a spec file can gain rather than a prediction of it, because a file also pays for imports,
|
|
2114
|
+
the `TestBed` module and the assertions. Link to
|
|
2115
|
+
`/core/performance#_2-rendering-the-child-subtree`. A speed figure for the helper also sits in
|
|
2116
|
+
`PRIORITIES.md`, backed by nothing in this repository — do not publish it until someone produces
|
|
2117
|
+
the measurement.
|
|
2070
2118
|
- **Competitor defects are quoted from the published tarball, with a file, a line and a date.** The
|
|
2071
2119
|
`@testing-library/angular` `createMock` claims in `docs-site/comparison.md` are pinned to
|
|
2072
2120
|
`fesm2022/testing-library-angular-vitest-utils.mjs` lines 14 and 18 at 19.4.2. Re-`npm pack` and
|
|
2073
2121
|
re-read before restating them against a new version; the "Where the numbers come from" box records
|
|
2074
2122
|
each re-verification date rather than replacing the previous one.
|
|
2123
|
+
- **Head-to-head benchmark numbers are quoted from the measurement, not from memory.** `npm run
|
|
2124
|
+
bench:vs` and `npm run bench:suite` (see `CONTRIBUTING.md` for setup) back the claims in
|
|
2125
|
+
`core/performance.md`; point at that page rather than retyping a figure here, so it lives in one
|
|
2126
|
+
place. Micro-benchmark ratios do not transfer to suite scale: double construction is on the order
|
|
2127
|
+
of 1% of a test's cost, and an advantage that looks like several times faster per double can be
|
|
2128
|
+
gone, or reversed, once a whole suite runs. Suite-scale wall-clock is noisy between invocations;
|
|
2129
|
+
peak RSS reproduces far more tightly and is the safer claim. Never state a magnitude the source
|
|
2130
|
+
marks as not established.
|
|
2075
2131
|
|
|
2076
2132
|
`node:test` keeps every `mock.fn()` in one process-wide `MockTracker` for the life of the process.
|
|
2077
2133
|
On a long suite that is real memory — 20 000 spies of a 10-method class retained 124.5 MB on
|
|
@@ -2521,6 +2577,35 @@ worth one run after any large edit to a test suite — especially after a codemo
|
|
|
2521
2577
|
the eaten glob below came from. Full reference:
|
|
2522
2578
|
<https://asdalexey.github.io/vitest-auto-spy/utilities/cli>.
|
|
2523
2579
|
|
|
2580
|
+
### If you were asked why a suite is slow
|
|
2581
|
+
|
|
2582
|
+
```bash
|
|
2583
|
+
npx vitest-auto-spy perf # runs the suite once and reports; always exits 0
|
|
2584
|
+
npx vitest-auto-spy perf src/some/dir # a path is passed through to Vitest as a file filter
|
|
2585
|
+
```
|
|
2586
|
+
|
|
2587
|
+
It reads Vitest's own per-file phase timings through `TestModule.diagnostic()` — nothing here
|
|
2588
|
+
parses terminal output — and reports six phases: `environment`, `import`, `tests`, `setup`,
|
|
2589
|
+
`prepare` (all measured per file) and `transform` (measured once for the whole run). **The phase
|
|
2590
|
+
totals are CPU time summed across every worker, not wall clock** — a report showing `20.29s of CPU
|
|
2591
|
+
time` under `986ms wall clock` is not a bug, it is the work spread over workers.
|
|
2592
|
+
|
|
2593
|
+
When `environment` dominates, the `perf-environment` finding names spec files that could run under
|
|
2594
|
+
the `node` environment instead — but only the ones it could _prove_ reach no DOM: the spec, its
|
|
2595
|
+
configured setup files, and every repository module any of them imports were all read, none
|
|
2596
|
+
mentions a DOM name, and every package they import is on a short allowlist (`vitest`, `rxjs`,
|
|
2597
|
+
`date-fns`, `lodash`, `zod`, …). Anything it could not resolve is reported **undecided**, never
|
|
2598
|
+
assumed safe — do not treat an undecided file as a candidate, and do not add packages to that list
|
|
2599
|
+
yourself; a false positive breaks someone's suite on `document is not defined`. When `import`
|
|
2600
|
+
dominates, `perf-import` names specs that reach their subject through a barrel (`index`/`public-api`
|
|
2601
|
+
re-exporting a whole directory). When `environment` + `setup` + `prepare` together dominate,
|
|
2602
|
+
`perf-isolation` suggests `test.isolate: false` and links to this package's own memory measurements
|
|
2603
|
+
of that trade rather than restating them — read that page before recommending the flag, since it
|
|
2604
|
+
raises peak memory.
|
|
2605
|
+
|
|
2606
|
+
`--json <path>` re-analyses a report an earlier `--out <path>` run wrote, instead of running Vitest
|
|
2607
|
+
again. Full reference: <https://asdalexey.github.io/vitest-auto-spy/utilities/cli>.
|
|
2608
|
+
|
|
2524
2609
|
### Migrating a suite off `jest-auto-spies` — run the codemod, then verify it
|
|
2525
2610
|
|
|
2526
2611
|
```bash
|
package/README.md
CHANGED
|
@@ -11,17 +11,19 @@ skip the class entirely and mock straight from a **type or interface** with `cre
|
|
|
11
11
|
recursive `mockDeep<T>()`. Runs on **Vitest**, **Bun** (`bun:test`) and **`node:test`** behind one
|
|
12
12
|
identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia / Svelte** recipes
|
|
13
13
|
([availability](#availability)). A drop-in replacement for
|
|
14
|
-
[`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — same API
|
|
14
|
+
[`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — same API, and roughly 1.5x
|
|
15
|
+
faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
15
16
|
[`jasmine-auto-spies`](https://www.npmjs.com/package/jasmine-auto-spies) through
|
|
16
17
|
[`vitest-auto-spy/jasmine`](#migrating-from-jasmine-auto-spies), which keeps `.and`, `.calls` and
|
|
17
18
|
`.withArgs` working while you land the suite green.
|
|
18
19
|
|
|
19
20
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
21
|
+
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
20
22
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
21
23
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
22
24
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
23
25
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
24
|
-
[](#install)
|
|
25
27
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
26
28
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
27
29
|
[](./LICENSE)
|
|
@@ -53,7 +55,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
53
55
|
- 📡 First-class RxJS `Observable` spying (`nextWith`, `nextWithValues`, `throwWith`, …)
|
|
54
56
|
- ⚙️ Getter / setter spies via `accessorSpies` — **including on Bun**, where `bun:test`'s own `spyOn(obj, 'prop', 'get')` throws _"does not support accessor properties yet"_ (verified on Bun 1.4.0); no library that generates a double from a class or a type has accessor spies on any runner
|
|
55
57
|
- 🧰 DI & mocking utilities — `provideAutoSpy` / `injectSpy` (Angular, NestJS, Vue), `createFunctionSpy`, `mockReadonlyProp` for signals
|
|
56
|
-
- ⚡ Angular speed & zoneless helpers — `renderShallow`
|
|
58
|
+
- ⚡ Angular speed & zoneless helpers — [`renderShallow`](#shallow-component-rendering), which brings a component up through the real `TestBed` without its child subtree, `createWithAutoSpies`, `stable` / `flushEffects`, `settleResource` for `httpResource()`, `toHaveSignalValue`, per-file `TestBed` timings
|
|
57
59
|
- **`node:test` stops retaining every spy.** Node's built-in runner keeps every `mock.fn()` in one
|
|
58
60
|
process-wide `MockTracker` and has no way to drop a single entry, so a long suite accumulates every
|
|
59
61
|
spy it ever made along with every argument they recorded. `trackNodeMocks()` gives this library a
|
|
@@ -75,7 +77,8 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
75
77
|
- 🚚 A migration you can verify — `vitest-auto-spy/diagnostics`: `compareTestRuns` on the two JSON reports, `summarizeTestRun` / `formatTestRunComparison` to read the answer, `diffByField` for the assertion the reporter collapses
|
|
76
78
|
- 📏 Lint rules and one-line test-run hygiene — nineteen rules in `vitest-auto-spy/eslint-plugin` (three `--fix`, seven suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()`
|
|
77
79
|
- 🩺 [Editor diagnostics](#editor-diagnostics--webstorm--vs-code) — the same anti-patterns underlined while you type: native ESLint inspections in **WebStorm** and the other JetBrains IDEs, the ESLint extension in **VS Code**, no extra plugin either way
|
|
78
|
-
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-codemod-and-init) — suite-level defects **that never fail a run**: a `tsconfig` `include` matching no file, a production module importing a spec, a `@jest-environment` pragma the runner never reads, config left behind for a runner that is gone. Read-only, no config, exits 1 in CI
|
|
80
|
+
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-perf-codemod-and-init) — suite-level defects **that never fail a run**: a `tsconfig` `include` matching no file, a production module importing a spec, a `@jest-environment` pragma the runner never reads, config left behind for a runner that is gone. Read-only, no config, exits 1 in CI
|
|
81
|
+
- ⏱️ [`npx vitest-auto-spy perf`](#perf--where-the-cpu-time-actually-goes) — where a suite's CPU time actually goes, phase by phase, and which spec files to act on: the ones that reach no DOM and could run under `node`, the ones that import a barrel. Runs Vitest once with a reporter this package ships, reads `TestModule.diagnostic()`, names files, states the rule behind each finding
|
|
79
82
|
- 🚚 [`npx vitest-auto-spy codemod`](#codemod--migrating-a-suite-off-jest-auto-spies) — thirteen transforms that move a suite off `jest-auto-spies` and Jest, or off `jasmine-auto-spies` and jasmine (`--from jasmine`), dry-run by default, with a `--verify` pass that also checks a file somebody edited by hand
|
|
80
83
|
- 🔇 Console spies — `import { consoleInfoSpy } from 'vitest-auto-spy/console'` silences `console` and asserts its calls
|
|
81
84
|
- 🧭 [**Spec patterns**](https://asdalexey.github.io/vitest-auto-spy/recipes) — the shapes a ~370-file Angular suite converged on, and the traps that only surface at scale
|
|
@@ -104,8 +107,9 @@ includes — that one import is what keeps `returnSubject()` typed as rxjs's own
|
|
|
104
107
|
- [Install](#install)
|
|
105
108
|
- [Requirements](#requirements)
|
|
106
109
|
- [Peer dependencies](#peer-dependencies)
|
|
107
|
-
- [The CLI — `doctor`, `codemod` and `init`](#the-cli--doctor-codemod-and-init)
|
|
110
|
+
- [The CLI — `doctor`, `perf`, `codemod` and `init`](#the-cli--doctor-perf-codemod-and-init)
|
|
108
111
|
- [`doctor` — defects that never fail](#doctor--defects-that-never-fail)
|
|
112
|
+
- [`perf` — where the CPU time actually goes](#perf--where-the-cpu-time-actually-goes)
|
|
109
113
|
- [`codemod` — migrating a suite off `jest-auto-spies`](#codemod--migrating-a-suite-off-jest-auto-spies)
|
|
110
114
|
- [`init` — the pointer an agent reads](#init--the-pointer-an-agent-reads)
|
|
111
115
|
- [Using this library with an AI agent](#using-this-library-with-an-ai-agent)
|
|
@@ -138,6 +142,8 @@ includes — that one import is what keeps `returnSubject()` typed as rxjs's own
|
|
|
138
142
|
- [Angular on Bun (`bun:test`)](#angular-on-bun-buntest)
|
|
139
143
|
- [Comparison](#comparison)
|
|
140
144
|
- [@testing-library/angular /vitest-utils](#testing-libraryangular-vitest-utils)
|
|
145
|
+
- [Benchmarks](#benchmarks)
|
|
146
|
+
- [Reproducing the numbers](#reproducing-the-numbers)
|
|
141
147
|
- [Migrating from jest-auto-spies](#migrating-from-jest-auto-spies)
|
|
142
148
|
- [Migrating from jasmine-auto-spies](#migrating-from-jasmine-auto-spies)
|
|
143
149
|
- [Migrating from @ngneat/spectator](https://asdalexey.github.io/vitest-auto-spy/migrating-spectator)
|
|
@@ -217,7 +223,7 @@ all**, because Vite 7 calls `crypto.hash` (added in Node 20.12) and the run dies
|
|
|
217
223
|
`TypeError: crypto.hash is not a function` before a spec loads. On Vitest ≤ 3 Node 18 is fine. Which
|
|
218
224
|
version to actually run is measured in
|
|
219
225
|
[Performance → Which Node version](https://vitest-auto-spy.dev/core/performance#which-node-version):
|
|
220
|
-
the
|
|
226
|
+
the break is between 22 and 24, where a cold import more than halves.
|
|
221
227
|
|
|
222
228
|
Ships **ESM with bundled `.d.ts` types**. Two subpaths additionally ship a CommonJS build —
|
|
223
229
|
`vitest-auto-spy/node` (a `node --test` suite written in CJS) and `vitest-auto-spy/eslint-plugin`
|
|
@@ -238,12 +244,13 @@ them only for the matching entry point. The package itself has **zero runtime de
|
|
|
238
244
|
| `rxjs` | `vitest-auto-spy/rxjs` observable spies — `>=7`, **no upper bound** (the rxjs 8 line included); not needed to type-check a spy since 4.0.0 | yes |
|
|
239
245
|
| `@angular/core` | `vitest-auto-spy/angular` helpers | yes |
|
|
240
246
|
|
|
241
|
-
## The CLI — `doctor`, `codemod` and `init`
|
|
247
|
+
## The CLI — `doctor`, `perf`, `codemod` and `init`
|
|
242
248
|
|
|
243
249
|
The package ships one executable, with no dependencies and nothing to configure:
|
|
244
250
|
|
|
245
251
|
```bash
|
|
246
252
|
npx vitest-auto-spy doctor # read-only. Exits 1 when it finds something
|
|
253
|
+
npx vitest-auto-spy perf # where the suite's CPU time goes. Always exits 0
|
|
247
254
|
npx vitest-auto-spy codemod # prints the migration diff. Writes nothing without --write
|
|
248
255
|
npx vitest-auto-spy init # writes the agent instructions pointer
|
|
249
256
|
```
|
|
@@ -291,6 +298,56 @@ spec tsconfigs still covered their specs.
|
|
|
291
298
|
|
|
292
299
|
`doctor` never writes. There is no `--fix`.
|
|
293
300
|
|
|
301
|
+
### `perf` — where the CPU time actually goes
|
|
302
|
+
|
|
303
|
+
Vitest prints one summary line per run with a phase breakdown; it is the only place those numbers
|
|
304
|
+
surface, and it is for the whole suite, not a file. `perf` reads the same numbers per file — through
|
|
305
|
+
`TestModule.diagnostic()`, Vitest's own public accessor, via a reporter this package ships — and
|
|
306
|
+
turns whichever phase dominates into a list of files and the rule that put them there. Nothing here
|
|
307
|
+
parses terminal output, and it always exits 0: a slow suite is not a failing one.
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
npx vitest-auto-spy perf # run the whole suite once and report
|
|
311
|
+
npx vitest-auto-spy perf src/cli # path passed through to Vitest as a file filter
|
|
312
|
+
npx vitest-auto-spy perf --json out/perf.json # re-analyse a report instead of running Vitest
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
$ npx vitest-auto-spy perf src/cli
|
|
317
|
+
vitest-auto-spy perf — /path/to/repo
|
|
318
|
+
16 test files, 860ms wall clock, 17.30s of CPU time summed over the workers
|
|
319
|
+
|
|
320
|
+
phase time share
|
|
321
|
+
prepare 6.34s 36.7%
|
|
322
|
+
environment 5.46s 31.6%
|
|
323
|
+
setup 3.01s 17.4%
|
|
324
|
+
transform 1.19s 6.9%
|
|
325
|
+
import 879ms 5.1%
|
|
326
|
+
tests 411ms 2.4%
|
|
327
|
+
|
|
328
|
+
info perf-environment
|
|
329
|
+
Environment setup is 31.6% of the measured CPU time, against 2.4% in the test bodies. No
|
|
330
|
+
spec file could be proved DOM-free, so this names none; 109 were left undecided.
|
|
331
|
+
→ Move what does not need a DOM to the `node` environment. …
|
|
332
|
+
|
|
333
|
+
0 errors, 0 warnings, 2 notes
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
That is this repository's own suite: `perf` names zero DOM-free candidates and leaves the rest
|
|
337
|
+
undecided, because `src/test-setup.ts` builds an Angular `TestBed` before every spec. The rule is
|
|
338
|
+
deliberately one-sided — a spec is a candidate only when it, the setup files, and every module they
|
|
339
|
+
import were read, none mentions a DOM name, and every package they import is on a short
|
|
340
|
+
DOM-free allowlist. Anything the rule cannot resolve is **undecided**, never assumed safe: a false
|
|
341
|
+
positive is somebody's suite failing on `document is not defined`.
|
|
342
|
+
|
|
343
|
+
**The phase totals are CPU time summed across workers**, which is why 860ms of wall clock reads as
|
|
344
|
+
17.30s of CPU above — the six phases are `environment`, `prepare`, `import`, `setup`, `tests` (all
|
|
345
|
+
per file) and `transform` (whole run). Where the isolation finding suggests `test.isolate: false`,
|
|
346
|
+
it links to this package's own memory measurements rather than repeating the numbers here — that
|
|
347
|
+
flag trades per-file cleanup for memory that grows with the suite.
|
|
348
|
+
|
|
349
|
+
Full reference, phase by phase and finding by finding: **[The CLI](https://asdalexey.github.io/vitest-auto-spy/utilities/cli)**.
|
|
350
|
+
|
|
294
351
|
### `codemod` — migrating a suite off `jest-auto-spies`
|
|
295
352
|
|
|
296
353
|
```bash
|
|
@@ -1129,6 +1186,178 @@ since 19.2.0 (2026-03-17), present in the tarball's `exports` map. That is a rea
|
|
|
1129
1186
|
favour. Full write-up, including ng-mocks and Spectator:
|
|
1130
1187
|
[Comparison → Angular](https://asdalexey.github.io/vitest-auto-spy/comparison#angular).
|
|
1131
1188
|
|
|
1189
|
+
## The spy engine
|
|
1190
|
+
|
|
1191
|
+
A method spy is not a `vi.fn()`. It is this library's own mock function — one shared prototype
|
|
1192
|
+
carrying the whole `Mock` surface, call state allocated on the first call rather than at creation,
|
|
1193
|
+
and no entry in any global registry. `vi.fn()` assigns some twenty-five closures as own properties
|
|
1194
|
+
of every mock it creates and allocates six arrays up front, per method, on every double a spec
|
|
1195
|
+
builds; on a service whose methods a test mostly never calls, that is the bill.
|
|
1196
|
+
|
|
1197
|
+
Everything a spec can observe is unchanged, and the suite pins it by putting a spy and a `vi.fn()`
|
|
1198
|
+
through the same steps and comparing their state:
|
|
1199
|
+
|
|
1200
|
+
- `vi.isMockFunction`, every `expect` matcher (`toHaveBeenCalledWith`, `toHaveReturned`,
|
|
1201
|
+
`toHaveResolved`, `toHaveBeenNthCalledWith`, …), and the failure messages that name the mock.
|
|
1202
|
+
- `spy.method.mock.calls` / `.results` / `.settledResults` / `.instances` / `.contexts` /
|
|
1203
|
+
`.lastCall`, the whole `mockReturnValue` / `mockResolvedValue` / `mockImplementation` family,
|
|
1204
|
+
`withImplementation`, `mockClear` / `mockReset` / `mockRestore`, `mockName`, `using`.
|
|
1205
|
+
- `vi.clearAllMocks()`, `vi.resetAllMocks()`, and the `clearMocks` / `mockReset` config keys, which
|
|
1206
|
+
Vitest applies through those two.
|
|
1207
|
+
|
|
1208
|
+
It is measured in bytes as well as microseconds. A materialised method went from **5 445 B to
|
|
1209
|
+
1 929 B** — the hand-written control, which is the runner's own mock with no library in the way,
|
|
1210
|
+
retains 5 169 B — and an eagerly built double that nothing calls went from 4 418 to **632 B per
|
|
1211
|
+
method**, because a spy that is never called now allocates none of the six arrays `vi.fn()`
|
|
1212
|
+
allocates up front. Full tables:
|
|
1213
|
+
[Performance → retained memory per double](https://asdalexey.github.io/vitest-auto-spy/core/performance#retained-memory-per-double).
|
|
1214
|
+
|
|
1215
|
+
**The one difference.** `mock.invocationCallOrder` counts on this library's own scale, so
|
|
1216
|
+
`expect(a).toHaveBeenCalledBefore(b)` is exact between two auto-spies and meaningless between an
|
|
1217
|
+
auto-spy and a hand-written `vi.fn()`. If a suite needs that comparison, switch the run back:
|
|
1218
|
+
|
|
1219
|
+
```ts
|
|
1220
|
+
// vitest.setup.ts
|
|
1221
|
+
import { setSpyEngine } from 'vitest-auto-spy/setup';
|
|
1222
|
+
|
|
1223
|
+
setSpyEngine('runner'); // every double built afterwards uses vi.fn() per method
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
The switch is Vitest-only. On Bun and `node:test` the runner's own matchers recognise only the
|
|
1227
|
+
runner's own mocks, so those entries keep building spies from `mock()` and `t.mock.fn()`.
|
|
1228
|
+
|
|
1229
|
+
## Benchmarks
|
|
1230
|
+
|
|
1231
|
+
Wall-clock at suite scale is the noisiest thing here, and the noise does not disappear inside a
|
|
1232
|
+
single invocation: the suite harness prints its own widest round-to-round spread, and in the run
|
|
1233
|
+
below that was **16%** of the cell's median — the three rounds of the 1 000-test cell came out at
|
|
1234
|
+
1.35 / 1.33 / 1.32 s and the 10 000-test cell at 10.02 / 10.65 / 9.72 s. Peak RSS, by contrast,
|
|
1235
|
+
reproduces tightly and separates arms by multiples. Treat the ratios below as the durable figure and
|
|
1236
|
+
any single wall-clock number as describing one machine, not a promise about yours.
|
|
1237
|
+
|
|
1238
|
+
**The micro-benchmark below has the same problem, measured directly.** Every micro figure is the
|
|
1239
|
+
**median p75 of seven independent runs**, each its own process, at doubled iteration budgets, run
|
|
1240
|
+
with `npm run bench:vs:precise` — not a single run. What each row's ± column reports is how far that
|
|
1241
|
+
published median can be off: across the 47 rows of the 2026-09-04 run, a median of **±0.9%** and at
|
|
1242
|
+
worst **±6.3%** — on the `calledWith` dispatch row, the fastest and therefore the one closest to the
|
|
1243
|
+
stand's own floor. The blunter rule still holds for reading a single local run — **a difference under
|
|
1244
|
+
about 20% is not worth quoting** — and the narrowest margin below is 2.19×.
|
|
1245
|
+
|
|
1246
|
+
`jest-auto-spies@3.0.1`, `jasmine-auto-spies@8.0.1` and `@bugsplat/vitest-auto-spies@1.0.0` are all
|
|
1247
|
+
measured directly here — no more standing in for one another. All three depend on
|
|
1248
|
+
`@hirez_io/auto-spies-core@3.0.0` and differ only in the spy factory they hand it (`jest.fn()`,
|
|
1249
|
+
`jasmine.createSpy()`, `vi.fn()`), and on the micro-benchmark they land within a few per cent of each
|
|
1250
|
+
other on every case — see the 40-methods/3-called row in
|
|
1251
|
+
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance#micro-benchmark).
|
|
1252
|
+
`jest-auto-spies` and `jasmine-auto-spies` run here under a minimal `jest` / `jasmine` global backed
|
|
1253
|
+
by `vi.fn()`, so every arm creates the same underlying mock and the runner's own per-mock cost is a
|
|
1254
|
+
shared constant — what these numbers describe is each library's own code, not what a real Jest or
|
|
1255
|
+
Jasmine suite would show.
|
|
1256
|
+
|
|
1257
|
+
The micro-benchmark runs every arm inside a block to the same iteration count (`n`), not the same
|
|
1258
|
+
time budget — these cases allocate doubles by the tens of thousands, and GC cost scales with objects
|
|
1259
|
+
created rather than elapsed time, so a fixed time budget would have handed a faster arm more
|
|
1260
|
+
allocations and made it pay for its own speed.
|
|
1261
|
+
|
|
1262
|
+
**Every micro-benchmark table is this library's**, and the margin is against the *best* other arm in
|
|
1263
|
+
that table. The three class sizes are measured project profiles, not round numbers — across four
|
|
1264
|
+
private Angular suites (~2 700 spec files, 2 742 doubles built from a class) the median service has
|
|
1265
|
+
5-8 methods and the spec touches 1 of them:
|
|
1266
|
+
|
|
1267
|
+
| Case (micro) | 4.0 | 4.1 | best other arm | lead |
|
|
1268
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
1269
|
+
| small project — 6 methods, 1 called | — | **1.42 µs** | 6.83 µs hand-written | 4.82x |
|
|
1270
|
+
| medium project — 14 methods, 2 called | — | **2.67 µs** | 15.92 µs hand-written | 5.97x |
|
|
1271
|
+
| large project — 45 methods, 2 called | — | **5.79 µs** | 52.79 µs hand-written | 9.11x |
|
|
1272
|
+
| worst case — all 14 of 14 methods called | 18.92 µs (0.66x, a loss) | **8.17 µs** | 17.92 µs hand-written | 2.19x |
|
|
1273
|
+
| worst case — all 45 of 45 methods called | 75.33 µs (0.71x, a loss) | **26.12 µs** | 62.04 µs hand-written | 2.37x |
|
|
1274
|
+
| double from a type — 2 / 10 / 40 members | 3.58 / 18.17 / 72.88 µs (a loss) | **1.00 / 4.67 / 18.92 µs** | 2.79 / 13.83 / 56.79 µs vitest-mock-extended | 2.79-3.00x |
|
|
1275
|
+
| deep double, 3 levels, leaf called | 8.83 µs (0.61x, a loss) | **2.29 µs** | 5.46 µs vitest-mock-extended | 2.38x |
|
|
1276
|
+
| configure a return + 3 calls — class / type | 3.29 / 2.08 µs (type a loss) | **2.21 / 0.71 µs** | 16.38 µs hand-written / 1.58 µs @golevelup | 7.41x / 2.24x |
|
|
1277
|
+
| `calledWith` dispatch, 2 configured + 1 miss | 0.54 µs (parity) | **0.17 µs** | 0.54 µs vitest-mock-extended | 3.25x |
|
|
1278
|
+
|
|
1279
|
+
Six of those rows were losses and one was parity. What changed is that a method spy is no longer a
|
|
1280
|
+
`vi.fn()` — see [The spy engine](#the-spy-engine) — plus one thing that is not the engine: a
|
|
1281
|
+
`calledWith(x)` of a single primitive argument used to be rendered into a string key on *every call*
|
|
1282
|
+
of that spy, where a `Map` keyed by the value does the same lookup with no allocation.
|
|
1283
|
+
|
|
1284
|
+
The three class sizes changed in the same release, from round numbers to measured profiles, so the
|
|
1285
|
+
first two columns are the same operation on a slightly different class where the row says so. Part
|
|
1286
|
+
of the lead is that this library no longer pays the runner's per-mock cost while every other arm
|
|
1287
|
+
still does — a difference in the product rather than in the measurement, and the `hand-written
|
|
1288
|
+
vi.fn() per method` arm is in the table precisely so its size stays visible.
|
|
1289
|
+
|
|
1290
|
+
**Where it still loses, same weight, same table** (`npm run bench:suite`, re-measured 2026-09-04 on
|
|
1291
|
+
the 4.1 build, 20-method class, `isolate: true`, coverage on, 3 rounds per cell after a discarded
|
|
1292
|
+
warm-up, medians of the three):
|
|
1293
|
+
|
|
1294
|
+
| Comparison | 1 000 tests | 3 000 tests | 10 000 tests |
|
|
1295
|
+
| --- | ---: | ---: | ---: |
|
|
1296
|
+
| vitest-auto-spy | 1.33 s | 2.86 s | 10.02 s |
|
|
1297
|
+
| hand-written `vi.fn()` | 1.31 s (0.99×) | 2.85 s (1.00×) | 9.25 s (0.92×) |
|
|
1298
|
+
| @bugsplat/vitest-auto-spies | 2.00 s (1.50×) | 4.61 s (1.61×) | 15.45 s (1.54×) |
|
|
1299
|
+
|
|
1300
|
+
Hand-written doubles are still cheaper — **about 3% at the median**, down from 10-15% before 4.1,
|
|
1301
|
+
with the nine per-round ratios spread 0.84-1.00 — and that row is the proof that micro-benchmark
|
|
1302
|
+
multipliers do not transfer to suite scale: building a double is on the order of 1% of a test's cost,
|
|
1303
|
+
so a 10× win on the double shows up as a few per cent on the run, and nearly everything else in a
|
|
1304
|
+
suite swamps it.
|
|
1305
|
+
|
|
1306
|
+
**Memory, `isolate: false`, 100-method class, 10 000 tests, peak RSS:**
|
|
1307
|
+
|
|
1308
|
+
| Arm | Peak RSS |
|
|
1309
|
+
| --- | ---: |
|
|
1310
|
+
| hand-written `vi.fn()` | 6366 MB |
|
|
1311
|
+
| vitest-auto-spy, default | 2103 MB |
|
|
1312
|
+
| vitest-auto-spy, `lazySpies: 'proxy'` | 1851 MB |
|
|
1313
|
+
|
|
1314
|
+
Hand-written doubles use 3.0x the default's peak RSS here — the difference between finishing and an
|
|
1315
|
+
OOM'd worker on a large suite.
|
|
1316
|
+
|
|
1317
|
+
That gap traces down to a single double: on the same 100-method class, untouched, `lazySpies:
|
|
1318
|
+
'proxy'` retains **4 097 B** per double against a hand-written double's **414 462 B** — the
|
|
1319
|
+
per-double number that gets multiplied by every double a file holds alive, and the setting that
|
|
1320
|
+
decides whether a large suite survives `isolate: false`. Full retained-memory tables:
|
|
1321
|
+
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance#retained-memory-per-double).
|
|
1322
|
+
|
|
1323
|
+
**Guidance keyed to suite size:** `isolate: false` is worth about 4x on its own — more than any
|
|
1324
|
+
library choice measured here — and it makes memory, not wall-clock, the binding constraint. On a
|
|
1325
|
+
wide class under `isolate: false`, add `lazySpies: 'proxy'` for roughly another 12% off peak RSS. Do
|
|
1326
|
+
not turn `'proxy'` on under normal isolation (`isolate: true`): there it measurably does nothing — a
|
|
1327
|
+
median of exactly 1.00× across five rounds.
|
|
1328
|
+
|
|
1329
|
+
Full write-up, methodology and the complete suite-scale tables:
|
|
1330
|
+
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance).
|
|
1331
|
+
|
|
1332
|
+
### Reproducing the numbers
|
|
1333
|
+
|
|
1334
|
+
Node **>=18** (the project floor); the numbers above were captured on Node v24.19.0. The steps are
|
|
1335
|
+
identical on Windows, macOS and Linux — same Node, same npm, same commands, nothing OS-specific to
|
|
1336
|
+
call out:
|
|
1337
|
+
|
|
1338
|
+
```bash
|
|
1339
|
+
git clone https://github.com/ASDAlexey/vitest-auto-spy.git
|
|
1340
|
+
cd vitest-auto-spy
|
|
1341
|
+
npm ci
|
|
1342
|
+
npm ci --prefix bench
|
|
1343
|
+
npm run bench:vs
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
Two installs are intentional: the competitor packages live in `bench/package.json`, kept out of the
|
|
1347
|
+
root so it carries nothing it doesn't ship or test with, and `bench/.npmrc` pins
|
|
1348
|
+
`legacy-peer-deps=true` so npm doesn't install a second copy of `vitest` beside the root one — two
|
|
1349
|
+
copies would mean two mock registries and the run dies out of memory.
|
|
1350
|
+
|
|
1351
|
+
`npm run bench:vs` is the micro-benchmark, a single run in about a minute — the right form for
|
|
1352
|
+
local iteration. `npm run bench:vs:precise` is what produced the median p75 figures published
|
|
1353
|
+
above, about eleven minutes for seven independent runs in seven processes. For the suite-scale tables, see
|
|
1354
|
+
`npm run bench:suite --help` — it takes **tens of minutes at 10 000 tests**, so budget for that
|
|
1355
|
+
before running it locally.
|
|
1356
|
+
|
|
1357
|
+
The same benchmark also runs in CI — on a monthly schedule, on any pull request touching `bench/**`
|
|
1358
|
+
or `src/lib/**`, and on demand — so a run you didn't trigger yourself is checkable in
|
|
1359
|
+
[Actions → Benchmarks](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/bench.yml).
|
|
1360
|
+
|
|
1132
1361
|
## Migrating from jest-auto-spies
|
|
1133
1362
|
|
|
1134
1363
|
The public API is intentionally identical. In most projects the migration is a
|
|
@@ -1648,9 +1877,9 @@ beforeEach(() => {
|
|
|
1648
1877
|
|
|
1649
1878
|
> **Lazy by default, everywhere.** Every factory builds each method spy on first access
|
|
1650
1879
|
> (`lazySpies: true`), since Angular tests typically spy a wide service but call
|
|
1651
|
-
> only a few of its methods —
|
|
1652
|
-
>
|
|
1653
|
-
> every spy eagerly.
|
|
1880
|
+
> only a few of its methods — and the margin widens as the class does: **1.4×** on a ten-method
|
|
1881
|
+
> class a test calls twice, **1.9×** on a forty-method one it calls three times. Behaviour is
|
|
1882
|
+
> unchanged; pass `{ lazySpies: false }` to build every spy eagerly.
|
|
1654
1883
|
|
|
1655
1884
|
#### Signal / readonly property mocking (bonus)
|
|
1656
1885
|
|
|
@@ -1717,46 +1946,24 @@ const { fixture, component } = renderShallow(TaskListComponent, {
|
|
|
1717
1946
|
|
|
1718
1947
|
`fixture` is a real `ComponentFixture`; nothing here replaces `@angular/core/testing`.
|
|
1719
1948
|
|
|
1720
|
-
**What it saves
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
the
|
|
1733
|
-
|
|
1734
|
-
by ±10%, which is this suite's run-to-run noise, so the two wins are outside it and the one
|
|
1735
|
-
regression is only just outside.
|
|
1736
|
-
|
|
1737
|
-
**Why 1.7× and not more.** That row is a whole spec file's wall clock — imports, the `TestBed`
|
|
1738
|
-
module, the assertions — of which rendering is only a part. Isolated to the render itself, the
|
|
1739
|
-
saving tracks the size of the subtree removed, measured on 2026-08-26 on a component holding two
|
|
1740
|
-
`@for` tables whose row count is varied:
|
|
1741
|
-
|
|
1742
|
-
| Child instances | `TestBed.createComponent` | `renderShallow` | Ratio |
|
|
1743
|
-
| --------------: | ------------------------: | --------------: | ----: |
|
|
1744
|
-
| 0 | 0.65 ms | 0.55 ms | 1.2× |
|
|
1745
|
-
| 10 | 1.00 ms | 0.55 ms | 1.8× |
|
|
1746
|
-
| 100 | 2.72 ms | 0.48 ms | 5.7× |
|
|
1747
|
-
| 400 | 8.52 ms | 0.53 ms | 16.2× |
|
|
1748
|
-
|
|
1749
|
-
`renderShallow` is flat at ~0.5 ms because it never builds the subtree; `createComponent` scales
|
|
1750
|
-
linearly with it. So there is no single "renderShallow is N× faster" number — a leaf component has
|
|
1751
|
-
nothing to save, and a table or a dashboard is an order of magnitude. Treat the ratios as the
|
|
1752
|
-
result, not the absolute times, and pick files to convert with the diagnostics below rather than by
|
|
1753
|
-
guessing. `keepTemplate: true` is the middle rung — the component's own template with an empty
|
|
1754
|
-
subtree, 1.933 ms → 1.074 ms (1.8×), measured 2026-08-30. Full write-up:
|
|
1949
|
+
**What it saves.** The shape is the point: `renderShallow` is flat in the number of children,
|
|
1950
|
+
because it never builds the subtree, while `TestBed.createComponent` scales linearly with it. So the
|
|
1951
|
+
win is not a fixed percentage and there is no single "renderShallow is N× faster" number — on a leaf
|
|
1952
|
+
component with no children there is nothing to remove, and the per-test `overrideComponent` can cost
|
|
1953
|
+
more than it saves; on a table or a dashboard the two curves are nowhere near each other. A
|
|
1954
|
+
per-render ratio is also the upper bound on what a whole spec file can gain rather than a prediction
|
|
1955
|
+
of it: a file's wall clock pays for imports, the `TestBed` module and the assertions too, none of
|
|
1956
|
+
which shallow rendering touches.
|
|
1957
|
+
|
|
1958
|
+
`keepTemplate: true` is the middle rung — the component's own template with an empty subtree.
|
|
1959
|
+
`buildOverride` applies `imports: keepChildren ?? []` whether or not the template is kept, so with
|
|
1960
|
+
`keepTemplate: true` the template renders while every child in it resolves to nothing under
|
|
1961
|
+
`NO_ERRORS_SCHEMA`. Reach for it when the spec reads something the template creates, and keep
|
|
1962
|
+
`keepChildren` for the handful of children it genuinely needs resolvable. Full write-up:
|
|
1755
1963
|
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance#_2-rendering-the-child-subtree).
|
|
1756
1964
|
|
|
1757
1965
|
Use [the diagnostics](#where-a-spec-spends-its-time) to find the files worth converting rather than
|
|
1758
|
-
guessing
|
|
1759
|
-
share ranged from 13% to 66%.
|
|
1966
|
+
guessing.
|
|
1760
1967
|
|
|
1761
1968
|
#### Building a class with auto-spied dependencies
|
|
1762
1969
|
|
|
@@ -2140,11 +2347,12 @@ Reach for `provideAutoSpy` on Angular and `createSpyFromClass` everywhere else;
|
|
|
2140
2347
|
whose members live on the instance) and `createMock<T>(partial)` for a data shape the code only
|
|
2141
2348
|
reads.
|
|
2142
2349
|
|
|
2143
|
-
None of it is worth optimising. On a ten-method class
|
|
2144
|
-
`createSpyFromClass`
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2350
|
+
None of it is worth optimising. On a ten-method class a test calls two methods of:
|
|
2351
|
+
`createSpyFromClass` **2.25 µs** per call, `createAutoMock` plus four accesses **1.79 µs**, a
|
|
2352
|
+
`calledWith` dispatch of three configured calls **0.21 µs** — five providers across two thousand
|
|
2353
|
+
tests is ten thousand calls, about two hundredths of a second for the whole run. `provideAutoSpy`
|
|
2354
|
+
is the same factory with the same lazy default, and prototype discovery is cached per class either
|
|
2355
|
+
way. Full numbers and the two settings that do cost something are in
|
|
2148
2356
|
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance).
|
|
2149
2357
|
|
|
2150
2358
|
## Utilities
|
|
@@ -2211,6 +2419,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
2211
2419
|
| `restoreTimerGlobals()` | `/setup` | Put back timer globals that uninstalling the fakes deleted rather than restored |
|
|
2212
2420
|
| `trackMockRegistry()` / `keepMockRegistered(mock)` / `restoreLongLivedImplementations()` | `/setup` | Keep @vitest/spy's mock registry to the mocks that outlive a file; mark one the split would miss; put back an implementation a cross-file `vi.resetAllMocks()` dropped ([details](#test-run-hygiene)) |
|
|
2213
2421
|
| `trackNodeMocks()` / `pruneNodeMocks()` / `countNodeMocks()` | `/node` | Give this library its own `node:test` `MockTracker` so a dropped spy is freed — 21× less retained heap; sweep by hand, and read the count back |
|
|
2422
|
+
| `setSpyEngine(engine)` / `getSpyEngine()` | `/setup` | Build method spies from this library's own mock (`'auto-spy'`, the default) or from `vi.fn()` (`'runner'`) ([details](#the-spy-engine)) |
|
|
2214
2423
|
| `errorHandler` | core | The `mustBeCalledWith` argument-mismatch reporter — swap it to customize failure output |
|
|
2215
2424
|
|
|
2216
2425
|
A taste of the DI pair — provide the spy, inject it back fully typed:
|
|
@@ -2871,7 +3080,7 @@ as `methodsToSpyOn`, named for callables that live on the instance — `signal()
|
|
|
2871
3080
|
`gettersToSpyOn`, `settersToSpyOn`, `autoSpyAccessors` (discover every getter/setter),
|
|
2872
3081
|
`fillMissing` (answer a name the prototype never carried with a spy — for a **partially** abstract
|
|
2873
3082
|
class, where `abstract` members are erased and the empty-prototype fallback no longer fires),
|
|
2874
|
-
`lazySpies` (materialize method spies on first access — cheaper for wide classes; the `provideAutoSpy` default on Angular. `'proxy'` keeps the laziness and drops the per-method placeholder
|
|
3083
|
+
`lazySpies` (materialize method spies on first access — cheaper for wide classes; the `provideAutoSpy` default on Angular. `'proxy'` keeps the laziness and drops the per-method placeholder — no memory crossover at any width measured, lighter than the default from 10 methods up (4 097 B vs the default's 25 601 B per double retained, untouched, on a 100-method class); it costs +30 ns per read once a method has materialised, which only pays back in **time** on classes above ~20 methods — see [Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance#retained-memory-per-double))
|
|
2875
3084
|
|
|
2876
3085
|
`ValueConfig` (for `nextWithValues`): `{ value, delay? }` | `{ errorValue, delay? }` | `{ complete?, delay? }`.
|
|
2877
3086
|
|