vitest-auto-spy 3.16.0 → 3.18.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 +218 -88
- package/README.md +197 -61
- package/dist/angular-http.d.ts +127 -0
- package/dist/angular-http.js +146 -0
- package/dist/angular.d.ts +5 -5
- package/dist/bun-angular.d.ts +4 -4
- package/dist/bun-angular.js +12 -47
- package/dist/bun.d.ts +4 -4
- package/dist/bun.js +6 -5
- package/dist/{chunk-52DZMI55.js → chunk-3QJUFL2N.js} +2 -1
- package/dist/chunk-5BPM3BLI.js +44 -0
- package/dist/{chunk-BNZFNS42.js → chunk-6EGUFU73.js} +2 -2
- package/dist/{chunk-4K5F2NVN.js → chunk-B45HLBA4.js} +3 -54
- package/dist/{chunk-YXHG5DXF.js → chunk-CJCRAL6X.js} +5 -4
- package/dist/{chunk-MMKKLL5J.js → chunk-DSIYS6Y5.js} +1 -1
- package/dist/{chunk-3EHP3NKE.js → chunk-N4PT3JYE.js} +3 -2
- package/dist/chunk-XRVVZXOM.js +56 -0
- package/dist/cli.js +16 -5
- package/dist/console.d.ts +1 -1
- package/dist/console.js +3 -2
- package/dist/{expect-emission-BhGKIi7_.d.ts → expect-emission-C-IjL1J2.d.ts} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/jasmine-compat.d.ts +1 -1
- package/dist/jasmine.d.ts +2 -2
- package/dist/jasmine.js +5 -4
- package/dist/nestjs.d.ts +68 -3
- package/dist/nestjs.js +170 -6
- package/dist/node.cjs +110 -14
- package/dist/node.d.cts +3 -0
- package/dist/node.d.ts +97 -3
- package/dist/node.js +103 -7
- package/dist/{prop-mock-B5TLaKCg.d.ts → prop-mock-DDtfdw9e.d.ts} +1 -1
- package/dist/react.d.ts +3 -3
- package/dist/react.js +7 -6
- package/dist/rxjs.d.ts +2 -2
- package/dist/rxjs.js +2 -1
- package/dist/setup.d.ts +17 -2
- package/dist/setup.js +104 -12
- package/dist/svelte.d.ts +3 -3
- package/dist/svelte.js +7 -6
- package/dist/{track-injections-DXoDu6sG.d.ts → track-injections-CzzXQwTl.d.ts} +1 -1
- package/dist/{types-d8Mdj_jK.d.ts → types-BvbrSBUX.d.ts} +16 -5
- package/dist/vue.d.ts +4 -4
- package/dist/vue.js +8 -7
- package/dist/{zoneless-BxqMRfiJ.d.ts → zoneless-D_hHdObW.d.ts} +7 -3
- package/package.json +14 -2
- package/skills/vitest-auto-spy/SKILL.md +9 -4
package/AGENTS.md
CHANGED
|
@@ -49,16 +49,17 @@ adapter installed and spies fail at runtime.
|
|
|
49
49
|
| Vue / Pinia | `vitest-auto-spy/vue` |
|
|
50
50
|
| Svelte | `vitest-auto-spy/svelte` |
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
| Add-on | Import | Needed for
|
|
55
|
-
| ----------------- | ------------------------------- |
|
|
56
|
-
| Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once.**
|
|
57
|
-
| observer-spy shim | `vitest-auto-spy/observer-spy` | `subscribeSpyTo` — the `@hirez_io/observer-spy` surface (§20). Its own entry so `/rxjs` does not carry it
|
|
58
|
-
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console`
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
52
|
+
Add-ons, orthogonal to the runner:
|
|
53
|
+
|
|
54
|
+
| Add-on | Import | Needed for |
|
|
55
|
+
| ----------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once.** |
|
|
57
|
+
| observer-spy shim | `vitest-auto-spy/observer-spy` | `subscribeSpyTo` — the `@hirez_io/observer-spy` surface (§20). Its own entry so `/rxjs` does not carry it |
|
|
58
|
+
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` |
|
|
59
|
+
| Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
|
|
60
|
+
| Setup helpers | `vitest-auto-spy/setup` | `setupAutoSpy()`, `setupFakeTimers()` |
|
|
61
|
+
| Zone patch | `import 'vitest-auto-spy/zone'` | `fakeAsync` / `waitForAsync` on Vitest (§14) |
|
|
62
|
+
| jasmine compat | `vitest-auto-spy/jasmine` | `.and` / `.calls` / `.withArgs`, the `jasmine` namespace (§20) |
|
|
62
63
|
|
|
63
64
|
`vitest-auto-spy/jasmine` is Vitest-only, because it registers the Vitest adapter. On `bun test` and
|
|
64
65
|
`node --test` call `enableJasmineCompat()` from `vitest-auto-spy/jasmine-compat` instead.
|
|
@@ -1115,6 +1116,21 @@ server is slow". One thing this cannot see through — a spec whose own `afterEa
|
|
|
1115
1116
|
`vi.useRealTimers()`, because hooks run in reverse registration order and the clock is real again by
|
|
1116
1117
|
the time the hint reads it. Nothing is reported then, rather than something wrong.
|
|
1117
1118
|
|
|
1119
|
+
`angularBuildHint` is the third read-only hint, and the only one that speaks before a test rather
|
|
1120
|
+
than after one. `@angular/build` in `[22.1.5, 22.1.7)` builds the unit-test bundle with esbuild code
|
|
1121
|
+
splitting off — every spec a self-contained bundle, `--coverage` growing by hundreds of megabytes
|
|
1122
|
+
with no plateau (791 chunks / 596 MB on a 784-spec suite) — and the builder says nothing. The
|
|
1123
|
+
`doctor` check `angular-build-splitting-off` reports it, but has to be run; this line is printed
|
|
1124
|
+
from inside the affected run, to stderr, once per worker. The builder is recognised by the marker
|
|
1125
|
+
its own `vitest-mock-patch` setup file sets (`Symbol.for('@angular/cli/vitest-mock-patch')` on
|
|
1126
|
+
`globalThis`, before any user setup file), so a plain Vitest run reads nothing; under the builder
|
|
1127
|
+
the version comes from the nearest `node_modules/@angular/build/package.json` above the working
|
|
1128
|
+
directory. That read is the one place the library touches the disk — one file, read-only, through
|
|
1129
|
+
`process.getBuiltinModule` so `/setup` still loads with no `process` — and `scripts/check-dist.mjs`
|
|
1130
|
+
allows `node:fs` in `dist/setup.js` for exactly that. Silent outside the builder, outside the
|
|
1131
|
+
window, and on a Node before `getBuiltinModule`. `setupAutoSpy({ angularBuildHint: false })` turns
|
|
1132
|
+
it off.
|
|
1133
|
+
|
|
1118
1134
|
One more field of the same family differs quietly and changes only the report:
|
|
1119
1135
|
`slowTestThreshold` is `5` in Jest (**seconds**) and `300` in Vitest (**milliseconds**), so a
|
|
1120
1136
|
migrated suite starts marking most of its files slow. That is a unit change, not a regression.
|
|
@@ -1726,6 +1742,51 @@ is legitimately scope-empty, so a stripped scope that still has providers passes
|
|
|
1726
1742
|
`assertNgModuleScopes(...)` where you know what the module was supposed to bring. `deadSchemas` does
|
|
1727
1743
|
not fire when `declarations` is non-empty.
|
|
1728
1744
|
|
|
1745
|
+
### `httpResource()` and `HttpClient` in two lines — `vitest-auto-spy/angular-http`
|
|
1746
|
+
|
|
1747
|
+
```ts
|
|
1748
|
+
import { expectRequest, provideHttpTesting } from 'vitest-auto-spy/angular-http';
|
|
1749
|
+
|
|
1750
|
+
TestBed.configureTestingModule({ providers: [...provideHttpTesting()] });
|
|
1751
|
+
|
|
1752
|
+
const products = TestBed.runInInjectionContext(() => httpResource<Product[]>(() => '/api/products'));
|
|
1753
|
+
|
|
1754
|
+
await expectRequest('/api/products').flush([product]);
|
|
1755
|
+
|
|
1756
|
+
expect(products.value()).toEqual([product]); // no tick, no microtask, no detectChanges
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
`expectRequest` does the six steps a zoneless HTTP spec otherwise writes by hand: tick (an
|
|
1760
|
+
`httpResource()` created in an injection context has issued **nothing** until something does), inject
|
|
1761
|
+
the `HttpTestingController`, find the one matching request, flush it, let one microtask run so the
|
|
1762
|
+
response reaches the resource, tick again so the view reading it is current. Miss the first and
|
|
1763
|
+
`expectOne` reports a request that was never sent; miss the fifth and the assertion reads the
|
|
1764
|
+
resource's **default** value and passes.
|
|
1765
|
+
|
|
1766
|
+
| Call | Does |
|
|
1767
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
1768
|
+
| `provideHttpTesting(opts?)` | `provideHttpClient()` + `provideHttpClientTesting()`; `{ verifyOnTeardown }` defaults to `true` |
|
|
1769
|
+
| `expectRequest(matcher, opts?)` | tick, then the one match — `.request`, `.flush(body, opts?)`, `.error(status, opts?)` |
|
|
1770
|
+
| `expectNoRequest(matcher?, opts?)` | tick, then assert nothing matched; no argument means "nothing at all was requested" |
|
|
1771
|
+
| `verifyNoPendingRequests()` | the teardown check by hand, for mid-test use or a suite with `verifyOnTeardown: false` |
|
|
1772
|
+
|
|
1773
|
+
`matcher` is a URL (matched against either `url` or `urlWithParams`), a `RegExp` over
|
|
1774
|
+
`urlWithParams`, or a predicate `(request) => boolean`. `{ method }` is case-insensitive.
|
|
1775
|
+
`flush()` and `error()` are `async` because settling requires letting a microtask run — `await` them,
|
|
1776
|
+
and the next line reads the settled value.
|
|
1777
|
+
|
|
1778
|
+
Three things to know before reaching for it:
|
|
1779
|
+
|
|
1780
|
+
- **It does not re-export the core.** The one subpath that does not: it is a companion to
|
|
1781
|
+
`vitest-auto-spy/angular`, which stays the import for spies, `TestBed` helpers and `settleResource`.
|
|
1782
|
+
Staying narrow is what keeps `@angular/common` — an **optional** peer — out of every other entry.
|
|
1783
|
+
- **`settleResource` is unchanged and still the answer** whenever the wait is not tied to one
|
|
1784
|
+
request: a `resource()` with an async loader, an `rxResource`, a reload, anything not HTTP.
|
|
1785
|
+
- **`enableAngularDiagnostics({ pendingRequests })` is unchanged and cooperates.** Both take the open
|
|
1786
|
+
requests with the one-shot `match(() => true)`, so an unanswered request is reported once. For a
|
|
1787
|
+
suite using `provideHttpTesting()` everywhere the diagnostic is redundant; keep it on while any
|
|
1788
|
+
file still configures HTTP testing by hand.
|
|
1789
|
+
|
|
1729
1790
|
### Which collaborators the code asked for — `trackInjections`
|
|
1730
1791
|
|
|
1731
1792
|
Do **not** reach for `vi.mock('@app/services')` to answer "was this collaborator used". Register the
|
|
@@ -1959,6 +2020,56 @@ and type the value against the member's **own** type. For a signal-valued member
|
|
|
1959
2020
|
`mockSignalProp(service, 'state', initial)` over `gettersToSpyOn`: a spied getter returns `undefined`
|
|
1960
2021
|
until configured, a real signal keeps everything downstream reactive.
|
|
1961
2022
|
|
|
2023
|
+
**A spec importing `@ngneat/spectator`** is on a package whose repository is a 404 and which does not
|
|
2024
|
+
resolve on Angular 22 (it imports `@angular/platform-browser-dynamic/testing` without declaring it).
|
|
2025
|
+
`createSpyObject` → `createSpyFromClass`, `mockProvider` → `provideAutoSpy`, `spectator.inject` →
|
|
2026
|
+
`injectSpy`, `SpyObject<T>` → `Spy<T>`, `createComponentFactory` → `renderShallow`. There is no
|
|
2027
|
+
replacement here for `spectator.query`, the event helpers or the DOM matchers — leave those on
|
|
2028
|
+
`@testing-library/angular` rather than inventing an equivalent. Full mapping:
|
|
2029
|
+
[Migrating from @ngneat/spectator](https://asdalexey.github.io/vitest-auto-spy/migrating-spectator).
|
|
2030
|
+
|
|
2031
|
+
A suite coming off [`@suites/unit`](https://github.com/suites-dev/suites) maps one to one:
|
|
2032
|
+
`TestBed.solitary(S).compile()` → `createNestUnit(S)`, `TestBed.sociable(S).expose(D).compile()` →
|
|
2033
|
+
`createNestUnit(S, { expose: [D] })`, `unitRef.get` → `spies.get`, `.final(v)` →
|
|
2034
|
+
`{ provide, useValue }`, and the `await` goes away because the graph is built synchronously. The one
|
|
2035
|
+
thing to check by hand after the swap: Suites' double answers every property name, so a spec that
|
|
2036
|
+
stubbed a since-renamed method was passing over nothing and will now fail. Full mapping:
|
|
2037
|
+
<https://asdalexey.github.io/vitest-auto-spy/migrating-suites>.
|
|
2038
|
+
|
|
2039
|
+
- **`renderShallow` has no single speed number, and quoting one is a bug.** Per render it ranges
|
|
2040
|
+
from 1.2× (no children) to 16.2× (400 child instances) — `docs-site/core/performance.md`,
|
|
2041
|
+
measured 2026-08-26. Per spec file, where imports and the `TestBed` module are also on the clock,
|
|
2042
|
+
the published conversion is 1.7× over three specs with one of them a 0.8× regression. The two are
|
|
2043
|
+
not interchangeable: the per-render ratio is the upper bound on what a file can gain. Any surface
|
|
2044
|
+
that mentions the helper's speed quotes the range and links to
|
|
2045
|
+
`/core/performance#_2-rendering-the-child-subtree`. A figure of **4.1×** appears in
|
|
2046
|
+
`PRIORITIES.md` and is backed by nothing in this repository — do not publish it until someone
|
|
2047
|
+
produces the measurement.
|
|
2048
|
+
- **Competitor defects are quoted from the published tarball, with a file, a line and a date.** The
|
|
2049
|
+
`@testing-library/angular` `createMock` claims in `docs-site/comparison.md` are pinned to
|
|
2050
|
+
`fesm2022/testing-library-angular-vitest-utils.mjs` lines 14 and 18 at 19.4.2. Re-`npm pack` and
|
|
2051
|
+
re-read before restating them against a new version; the "Where the numbers come from" box records
|
|
2052
|
+
each re-verification date rather than replacing the previous one.
|
|
2053
|
+
|
|
2054
|
+
`node:test` keeps every `mock.fn()` in one process-wide `MockTracker` for the life of the process.
|
|
2055
|
+
On a long suite that is real memory — 20 000 spies of a 10-method class retained 124.5 MB on
|
|
2056
|
+
Node v24.19.0. `trackNodeMocks()` puts this library's spies on a tracker it owns and replaces it per
|
|
2057
|
+
test: 5.9 MB for the same run, against a 5.4 MB baseline.
|
|
2058
|
+
|
|
2059
|
+
\```ts
|
|
2060
|
+
import { before } from 'node:test';
|
|
2061
|
+
import { trackNodeMocks } from 'vitest-auto-spy/node';
|
|
2062
|
+
|
|
2063
|
+
before(() => trackNodeMocks()); // opt-in, idempotent, returns the undo
|
|
2064
|
+
|
|
2065
|
+
// by hand, for a concurrent suite: pruneNodeMocks() → how many were dropped; countNodeMocks() → how many are held
|
|
2066
|
+
\```
|
|
2067
|
+
|
|
2068
|
+
It never calls `mock.reset()` — that would restore and forget the `mock.fn()` the spec made itself —
|
|
2069
|
+
and it never throws: the class is reached through the undocumented `mock.constructor`, so the
|
|
2070
|
+
constructed tracker is probed first and any failure leaves spies on `node:test`'s own tracker.
|
|
2071
|
+
Spies created **before** the call stay there too. `mock.reset()` in `afterEach` is the fallback.
|
|
2072
|
+
|
|
1962
2073
|
---
|
|
1963
2074
|
|
|
1964
2075
|
## 14. `fakeAsync` needs `vitest-auto-spy/zone`
|
|
@@ -1991,9 +2102,14 @@ convenient re-export from the root: it would quietly hand zone.js to every zonel
|
|
|
1991
2102
|
|
|
1992
2103
|
```ts
|
|
1993
2104
|
// NestJS
|
|
1994
|
-
import { injectSpy, provideAutoSpy } from 'vitest-auto-spy/nestjs';
|
|
2105
|
+
import { createNestUnit, injectSpy, provideAutoSpy } from 'vitest-auto-spy/nestjs';
|
|
1995
2106
|
const moduleRef = await Test.createTestingModule({ providers: [provideAutoSpy(MyService)] }).compile();
|
|
1996
2107
|
const spy = injectSpy(moduleRef, MyService);
|
|
2108
|
+
// or no module at all: the unit built from its DI metadata, every unprovided token spied
|
|
2109
|
+
const { unit, spies } = createNestUnit(CartService, { expose: [PricingService], providers: [{ provide: 'CONFIG', useValue: cfg }] });
|
|
2110
|
+
spies.get(TaxService).rate.mockReturnValue(0.2); // spies.get refuses a token the unit never asked for, and an exposed class
|
|
2111
|
+
spies.autoSpiedTokens(); // what nothing provided — the list the refusal message prints; spies.exposedTokens() is what `expose` actually built
|
|
2112
|
+
// needs what Nest needs: emitDecoratorMetadata (tsc / SWC, not esbuild) and reflect-metadata loaded first
|
|
1997
2113
|
|
|
1998
2114
|
// Vue / Pinia — provideAutoSpy(token, Class, methodsOrConfig?) returns a `global.provide` map
|
|
1999
2115
|
import { provideAutoSpy } from 'vitest-auto-spy/vue';
|
|
@@ -2168,83 +2284,91 @@ packages, which a subpath export can never be.
|
|
|
2168
2284
|
|
|
2169
2285
|
## 17. Error → fix
|
|
2170
2286
|
|
|
2171
|
-
| Message contains
|
|
2172
|
-
|
|
|
2173
|
-
| `No mock adapter registered`
|
|
2174
|
-
| `Observable spies require rxjs`
|
|
2175
|
-
| `Cannot read properties of undefined (reading 'returnValue')` on `spy.m.and.…`
|
|
2176
|
-
| `spy.withArgs is not a function`
|
|
2177
|
-
| `jasmine is not defined`
|
|
2178
|
-
| `done.fail is not a function`
|
|
2179
|
-
| a `.withContext('…')` message that never appears in the failure output
|
|
2180
|
-
| `requested method(s) not found on the class prototype`
|
|
2181
|
-
| `was configured with 'mustBeCalledWith'`
|
|
2182
|
-
| `extendWithAutoSpies needs Vitest 4.1 or newer`
|
|
2183
|
-
| `advanceTimers() requires fake timers`
|
|
2184
|
-
| `Nothing configured X.method, and strict mode is on`
|
|
2185
|
-
| `Cannot redefine property: …` from a library accessor spy
|
|
2186
|
-
| `overrideComponentProvider(…): the override did not apply`
|
|
2187
|
-
| `the test ended with N unflushed HttpTestingController request(s)`
|
|
2188
|
-
| `NgModule(s) with an empty runtime scope: …`
|
|
2189
|
-
| `Cannot read properties of undefined (reading 'provide')`, stack inside `render3/di_setup`
|
|
2190
|
-
| `assertComponentDefIntact(): argument N is undefined, which carries no ɵcmp or ɵdir`
|
|
2191
|
-
| `TS1117: An object literal cannot have multiple properties with the same name`, in a fixture
|
|
2192
|
-
| `configureTestingModule was given N schema(s) that can never apply`
|
|
2193
|
-
| `injectSpy(X): the injector returned a plain instance, not an auto-spy`
|
|
2194
|
-
| `trackInjections(...).get(X): that token is not tracked by this log`
|
|
2195
|
-
| `Spread syntax requires ...iterable[Symbol.iterator] to be a function`, at load time
|
|
2196
|
-
| `the timers APIs are not mocked` in a nested `describe`'s `beforeAll`
|
|
2197
|
-
| setup-file hooks reaching only the first spec file of a worker
|
|
2198
|
-
| `no DOM could be installed`
|
|
2199
|
-
| `cannot read "…" referenced by …`
|
|
2200
|
-
| duplicate-copy report from `setupAutoSpy()`
|
|
2201
|
-
| `Type 'Spy<T>' is not assignable to type 'T'`
|
|
2202
|
-
| a spy is never called, no warning
|
|
2203
|
-
| `Cannot access '__vi_import_N__' before initialization`
|
|
2204
|
-
| `AggregateError at Object.dispatchError`, for a request nothing asserts on
|
|
2205
|
-
| `Schedulers cannot synchronously execute watches while scheduling`
|
|
2206
|
-
| `signal read during notification phase`
|
|
2207
|
-
| an assertion error printed to stderr, every test green and the run exiting 0
|
|
2208
|
-
| an `expect()` inside a `.then()` that never seems to run
|
|
2209
|
-
| `trackStrayRejections() found no zone.js on the host`
|
|
2210
|
-
| `… .destroy is not a function`
|
|
2211
|
-
| `NullInjectorError` for a service you did provide
|
|
2212
|
-
| `runEffect(): … not an EffectRef returned by effect()`
|
|
2213
|
-
| `X is not a constructor`, stack in production code
|
|
2214
|
-
| `Date is not a constructor`
|
|
2215
|
-
| `TS2352: … 'accessorSpies' is missing in type 'X'`
|
|
2216
|
-
| `TS2739` / `TS2740` / `TS2345` with `Spy<X>` on the left
|
|
2217
|
-
| `is missing the following properties: _private, …`
|
|
2218
|
-
| `AddPromiseSpyMethods<unknown>` vs `WithMockReturnValue<…>`
|
|
2219
|
-
| `'addEventListener' called on an object that is not a valid instance of EventTarget`
|
|
2220
|
-
| `vi.requireMock is not a function`
|
|
2221
|
-
| a `vi.mock()` factory that never applies, only sometimes
|
|
2222
|
-
| a `vi.mock()` of a workspace alias that never applies at all
|
|
2223
|
-
| `No "default" export is defined on the mock`, thrown inside a dependency
|
|
2224
|
-
| `Not implemented: HTMLMediaElement.play`, or `duration` is `NaN` and cannot be set
|
|
2225
|
-
| `Cannot set base providers because it has already been called`
|
|
2226
|
-
| a stub that works in the first test of the file and in no other
|
|
2227
|
-
| a third-party library failing every other run, no test named
|
|
2228
|
-
| `expected [ { at: 1, …(5) }, …(8) ] to deeply equal [ { …(6) }, … ]`
|
|
2229
|
-
| a hand-tuned number of turns waiting for a `resource()` to load
|
|
2230
|
-
| a `resource()` assertion that passes but reads the **default** value
|
|
2231
|
-
| a spec dying on the runner's 5 s file timeout right after `await stable(fixture)`
|
|
2232
|
-
| `flushEventLoopUntil` timing out on the **first** such test only, the rest green
|
|
2233
|
-
| `Expected to be running in 'ProxyZone', but it was not found`
|
|
2234
|
-
| `nextWith` demanding `HttpEvent<T>` on a generated API client
|
|
2235
|
-
| `NG0303` / `NG0304` / nothing at all, from a directive spec
|
|
2236
|
-
| `TS2540: Cannot assign to 'X' because it is a read-only property`
|
|
2237
|
-
| `TypeError: Cannot read properties of undefined (reading 'mockReturnValue')`
|
|
2238
|
-
| `throwWith` reaching the success branch first, with the previous test's value
|
|
2239
|
-
| `TS2540` on a `Spy<T>` / `createAutoMock` member, which the runtime writes fine
|
|
2240
|
-
| `delete mock.optionalMethod` leaving the member present and truthy
|
|
2241
|
-
| half a double's methods reaching the real implementation after `Object.assign`
|
|
2242
|
-
| `let s: MockInstance<() => unknown>` not matching anything
|
|
2243
|
-
| two runs with the same totals, one of them missing a suite
|
|
2244
|
-
| a 30 s timeout, in a different file each run
|
|
2245
|
-
| a component's `afterNextRender` state is empty
|
|
2246
|
-
| a green test whose only line about a call is `spy.m.calledWith(1);`
|
|
2247
|
-
| `--detect-async-leaks` reporting no leaks in a suite that visibly leaks timers
|
|
2287
|
+
| Message contains | Cause | Fix |
|
|
2288
|
+
| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2289
|
+
| `No mock adapter registered` | no runtime entry was imported, or the wrong one | import `vitest-auto-spy` (Vitest) / `…/bun` / `…/node` once before creating spies |
|
|
2290
|
+
| `Observable spies require rxjs` | the rxjs layer was never loaded | `import 'vitest-auto-spy/rxjs';` once, in the setup file |
|
|
2291
|
+
| `Cannot read properties of undefined (reading 'returnValue')` on `spy.m.and.…` | the jasmine namespaces are not installed — the spy was built before them, or nothing installed them at all | import from `vitest-auto-spy/jasmine`; on Bun / `node:test`, `enableJasmineCompat()` in the setup file (§20) |
|
|
2292
|
+
| `spy.withArgs is not a function` | the same, seen from the argument-matching side | the same — or write `spy.m.calledWith(a).mockReturnValue(v)`, which needs no layer (§20) |
|
|
2293
|
+
| `jasmine is not defined` | jasmine's global is the runner's, and Vitest declares none | `import { jasmine } from 'vitest-auto-spy/jasmine'`, then `codemod --from jasmine` to rewrite the members (§20) |
|
|
2294
|
+
| `done.fail is not a function` | Vitest passes a `TestContext`, not jasmine's `done`; the line usually sits in an `error` callback nobody awaits, so the run stays **green** | assert on the failure — `await expect(firstValueFrom(src$)).rejects.toMatchObject({ … })`, or `expect.fail(message)` |
|
|
2295
|
+
| a `.withContext('…')` message that never appears in the failure output | Vitest's chai layer has an `@internal` `withContext(flags)`; handed a string it walks the character indices, sets nonsense flags and returns `this` | `expect(actual, 'message').toBe(expected)` — the second argument of `expect` is the label. Nothing throws, so nothing warns (§20) |
|
|
2296
|
+
| `requested method(s) not found on the class prototype` | typo, or an instance-field callable | fix the name, or move it to `instanceMethodsToSpyOn` |
|
|
2297
|
+
| `was configured with 'mustBeCalledWith'` | the code called the spy with other arguments | that is the assertion firing — fix the code, or relax to `calledWith` |
|
|
2298
|
+
| `extendWithAutoSpies needs Vitest 4.1 or newer` | the `test` handed in has only the object-form `extend` (Vitest ≤ 4.0); the builder form the helper is written against arrived in 4.1 | upgrade Vitest, or keep `provideAutoSpy` + `injectSpy` in a `beforeEach` until then |
|
|
2299
|
+
| `advanceTimers() requires fake timers` | no fake timers installed | `setupFakeTimers()` or `vi.useFakeTimers()` first |
|
|
2300
|
+
| `Nothing configured X.method, and strict mode is on` | a strict double was asked for a method no line configured | configure it (`mockReturnValue` / `resolveWith` / `nextWith` / `calledWith`), or `{ strict: false }` on that double (§5) |
|
|
2301
|
+
| `Cannot redefine property: …` from a library accessor spy | the target is an ES module namespace a bundler inlined | no spy library can win this — give the code a real seam (inject it, pass it in) and spy on that |
|
|
2302
|
+
| `overrideComponentProvider(…): the override did not apply` | the component injects a different token, or a later `overrideProvider` won | pass the token the component actually injects (a base class, an `InjectionToken`), and override after nothing else re-configures |
|
|
2303
|
+
| `the test ended with N unflushed HttpTestingController request(s)` | `enableAngularDiagnostics({ pendingRequests })` — nothing answered them | flush each (`controller.expectOne(url).flush(body)`), or `controller.verify()` where absence is the assertion (§13) |
|
|
2304
|
+
| `NgModule(s) with an empty runtime scope: …` | `ngModuleScopes` (or `assertNgModuleScopes`) — an AOT test bundle stripped `ɵɵsetNgModuleScope`, so the import contributes nothing | declare what the spec needs in the TestBed module directly; pass only modules expected to bring declarations (§13) |
|
|
2305
|
+
| `Cannot read properties of undefined (reading 'provide')`, stack inside `render3/di_setup` | a barrel chunk had not run when the component's definition was built, so a provider slot is `undefined` | `assertComponentDefIntact(Cmp)` before `createComponent` — it names the list and the index (§13) |
|
|
2306
|
+
| `assertComponentDefIntact(): argument N is undefined, which carries no ɵcmp or ɵdir` | the class reference itself never arrived — the `Cannot read properties of undefined (reading 'ɵcmp')` case | the same split barrel; import the component from its own module, not the barrel (§13) |
|
|
2307
|
+
| `TS1117: An object literal cannot have multiple properties with the same name`, in a fixture | a hundred-line model literal copied into eight specs and edited independently | one `createFixtureFactory<T>(defaults)`; note the runtime keeps the **second** key, so do not auto-fix by dropping one (§12) |
|
|
2308
|
+
| `configureTestingModule was given N schema(s) that can never apply` | `enableAngularDiagnostics({ deadSchemas })` — `schemas` next to a standalone component, with `declarations` empty | drop the `schemas` entry and put the missing directive/pipe in the component's own `imports`, or use `createDirectiveHost` (§13) |
|
|
2309
|
+
| `injectSpy(X): the injector returned a plain instance, not an auto-spy` | the token is provided for real; a `console.warn` on its own, a **throw** under `enableAngularDiagnostics({ unspiedProviders })` | `provideAutoSpy(X)` / `provideAutoSpyForToken(TOKEN)`, or `TestBed.inject(X)` if the real thing is what the spec wants |
|
|
2310
|
+
| `trackInjections(...).get(X): that token is not tracked by this log` | `get` only answers for tokens whose providers the log created | add the token to the `trackInjections([...])` list, or read it from the injector directly (§13) |
|
|
2311
|
+
| `Spread syntax requires ...iterable[Symbol.iterator] to be a function`, at load time | a module-scope spread of an imported binding, inside a bundle | build the array in a function or a getter — `no-import-time-spread` (§16) |
|
|
2312
|
+
| `the timers APIs are not mocked` in a nested `describe`'s `beforeAll` | fakes armed in `beforeEach` only; Jest armed them for the whole file | `setupFakeTimers(cfg, { betweenTests: true })` / `setupAutoSpy({ globalFakeTimers: true })` |
|
|
2313
|
+
| setup-file hooks reaching only the first spec file of a worker | the setup module stayed cached (Angular unit-test builder + coverage) | run coverage with `--isolate`, or call `setupAutoSpy()` from a per-file module |
|
|
2314
|
+
| `no DOM could be installed` | `bun-angular` preload with no DOM package | `bun add -d @happy-dom/global-registrator` (or `jsdom`) |
|
|
2315
|
+
| `cannot read "…" referenced by …` | a `templateUrl` / `styleUrls` path does not resolve | fix the path, relative to the component file |
|
|
2316
|
+
| duplicate-copy report from `setupAutoSpy()` | two installs, or one loaded as both ESM and CJS | dedupe the dependency; `setupAutoSpy({ duplicateCopies: 'warn' })` to downgrade |
|
|
2317
|
+
| `Type 'Spy<T>' is not assignable to type 'T'` | `Spy<T>` drops private members — by design | declare as `Spy<T>`, or use `asInstance()` / `asSpy()` (§6) |
|
|
2318
|
+
| a spy is never called, no warning | the method is an instance field, not on the prototype | `instanceMethodsToSpyOn`, or `createAutoMock<T>()` |
|
|
2319
|
+
| `Cannot access '__vi_import_N__' before initialization` | `vi.mock()` on `@angular/core` or a relative path | you cannot mock it — the specs are bundled. Assert the result instead |
|
|
2320
|
+
| `AggregateError at Object.dispatchError`, for a request nothing asserts on | jsdom really served an `XMLHttpRequest` — `blockNetwork` used to cover only `fetch` | `setupAutoSpy({ blockNetwork: true })`, or `{ xhr: 'empty' }` for tracker pings (§10) |
|
|
2321
|
+
| `Schedulers cannot synchronously execute watches while scheduling` | a timer from a **previous** file, under `isolate: false` | track and cancel pending timers/frames in the setup file (§10) |
|
|
2322
|
+
| `signal read during notification phase` | same — a stray `requestAnimationFrame` callback | same |
|
|
2323
|
+
| an assertion error printed to stderr, every test green and the run exiting 0 | zone.js swallowed a rejection nobody handled | `setupAutoSpy({ strayRejections: true })` fails the test it surfaced in (§10) |
|
|
2324
|
+
| an `expect()` inside a `.then()` that never seems to run | nothing awaits the chain, so the test ended first | `await` the promise and assert the settled value — `no-floating-assertion` (§16) |
|
|
2325
|
+
| `trackStrayRejections() found no zone.js on the host` | `strayRejections` turned on where zone.js is not loaded | `import 'zone.js';` in the setup file, or drop the option |
|
|
2326
|
+
| `… .destroy is not a function` | an ngrx `rxMethod` replaced with a bare mock | `Object.assign(vi.fn(), { destroy: vi.fn() })` |
|
|
2327
|
+
| `NullInjectorError` for a service you did provide | it is a component-level provider, not a module one | `asSpy(fixture.debugElement.injector.get(X))`, not `injectSpy(X)` |
|
|
2328
|
+
| `runEffect(): … not an EffectRef returned by effect()` | passed the callback, a signal, or an unassigned field | pass what `effect()` returned; a field may need its lifecycle hook to run first |
|
|
2329
|
+
| `X is not a constructor`, stack in production code | a `vi.fn(() => …)` where the code does `new X()` | `mockConstructor` / `stubConstructor` / `createSpyClass` (§12) |
|
|
2330
|
+
| `Date is not a constructor` | `vi.spyOn(globalThis, 'Date')` — the fakes own it | `mockSystemTime(date)` / `vi.setSystemTime` |
|
|
2331
|
+
| `TS2352: … 'accessorSpies' is missing in type 'X'` | `TestBed.inject(X) as Spy<X>` | `asSpy(TestBed.inject(X))` — never a double assertion (§6) |
|
|
2332
|
+
| `TS2739` / `TS2740` / `TS2345` with `Spy<X>` on the left | a spy handed to an API typed against the class | `asInstance(spy)` (§6) |
|
|
2333
|
+
| `is missing the following properties: _private, …` | declared as Vitest's `Mocked<T>` | declare `Spy<T>` (§6) |
|
|
2334
|
+
| `AddPromiseSpyMethods<unknown>` vs `WithMockReturnValue<…>` | a generic class inferred as `Service<any>` | `asSpy<Service>(…)` / `injectSpy<Service>(…)` (§6) |
|
|
2335
|
+
| `'addEventListener' called on an object that is not a valid instance of EventTarget` | Node's `AbortSignal` under jsdom + zone.js | `stubAbortController()` (§12) |
|
|
2336
|
+
| `vi.requireMock is not a function` | a mechanical `jest.` → `vi.` rename | there is no equivalent — provide the double through the TestBed instead |
|
|
2337
|
+
| a `vi.mock()` factory that never applies, only sometimes | under `isolate: false` the module was already in the worker's graph | do not mock it; inject the dependency, or `vi.hoisted()` + a real seam |
|
|
2338
|
+
| a `vi.mock()` of a workspace alias that never applies at all | a bundler inlined the module before the mock could be installed | `assertMocked(ns, { specifier })` to prove it, then inject instead of mocking |
|
|
2339
|
+
| `No "default" export is defined on the mock`, thrown inside a dependency | a factory returning bare named exports; the dep probes `default` | `vi.mock('x', () => moduleNamespace({ … }))` |
|
|
2340
|
+
| `Not implemented: HTMLMediaElement.play`, or `duration` is `NaN` and cannot be set | jsdom implements the media elements as a shell | `stubMediaElement({ duration })`, then `media.set(el, …)` to fire the events |
|
|
2341
|
+
| `Cannot set base providers because it has already been called` | zone and zoneless spec files sharing one worker | `setupAngularTestEnv({ zoneless, initZone, initZoneless })` (§13) |
|
|
2342
|
+
| a stub that works in the first test of the file and in no other | installed at `describe` level or in `beforeAll`, then restored away | install it in `beforeEach`, or `installPerTest(() => stub…())` |
|
|
2343
|
+
| a third-party library failing every other run, no test named | a test sealed a global with `Object.defineProperty` (non-configurable) | `setupAutoSpy({ guardGlobals: 'throw' })` names the file; then `mockValueProp` |
|
|
2344
|
+
| `expected [ { at: 1, …(5) }, …(8) ] to deeply equal [ { …(6) }, … ]` | one field moved in every element — usually a frozen clock or an id | `expect(diffByField(actual, expected)).toBeUndefined()` |
|
|
2345
|
+
| a hand-tuned number of turns waiting for a `resource()` to load | a resource needs a change-detection **tick**, not event-loop turns; `flushEventLoopUntil` never ticks and the resource never even issues its request | `flushEffects()`, flush the request, then `await settleResource(r, { label })` |
|
|
2346
|
+
| a `resource()` assertion that passes but reads the **default** value | the spec asserted before the resource left `loading` | `await settleResource(r, { label })` — it fails loudly instead |
|
|
2347
|
+
| a spec dying on the runner's 5 s file timeout right after `await stable(fixture)` | the fixture never stabilised — an unflushed request, a real `setInterval` | `stable` now fails at 2000 ms naming the cause; raise `{ timeout }` only once neither is true |
|
|
2348
|
+
| `flushEventLoopUntil` timing out on the **first** such test only, the rest green | a cold dynamic `import()` outran the turn budget; later tests hit the module cache | `await settleDynamicImport(() => import('…'))` — await the module, do not count turns |
|
|
2349
|
+
| `Expected to be running in 'ProxyZone', but it was not found` | `zone.js/testing` patches jasmine/mocha/jest, not Vitest | `import 'vitest-auto-spy/zone'` after zone.js, with `globals: true` (§14) |
|
|
2350
|
+
| `nextWith` demanding `HttpEvent<T>` on a generated API client | `Parameters`/`ReturnType` read the **last** overload | `asSpy<Client, { overload: 'first' }>(…)`, or `Overload<M, 0>` |
|
|
2351
|
+
| `NG0303` / `NG0304` / nothing at all, from a directive spec | the host is `standalone: false`, or the module is in the TestBed | `createDirectiveHost({ template, scope: [Module] })` (§13) |
|
|
2352
|
+
| `TS2540: Cannot assign to 'X' because it is a read-only property` | a `readonly` field of an object under test | `mockValueProp(obj, 'X', value)` — on a class **getter**, `mockReadonlyProp` |
|
|
2353
|
+
| `TypeError: Cannot read properties of undefined (reading 'mockReturnValue')` | the member is an **instance field**, not on the prototype — `Router.currentNavigation` since Angular 20 | `provideAutoSpy(Router, { instanceMethodsToSpyOn: ['currentNavigation'] })` (§5) |
|
|
2354
|
+
| `throwWith` reaching the success branch first, with the previous test's value | the spy's `ReplaySubject(1)` outlived the test that filled it | upgrade; and `resetAutoSpy(spy)` in `beforeEach` when the TestBed is built in `beforeAll` |
|
|
2355
|
+
| `TS2540` on a `Spy<T>` / `createAutoMock` member, which the runtime writes fine | `Spy<T>` is homomorphic, so it keeps the `readonly` of an abstract getter | `Mutable<Spy<T>>` for direct assignment, or `mockValueProp` for a patch that is undone |
|
|
2356
|
+
| `delete mock.optionalMethod` leaving the member present and truthy | the Proxy had no `deleteProperty` trap before 3.5.0 | upgrade; before that, `mock.optionalMethod = undefined` |
|
|
2357
|
+
| half a double's methods reaching the real implementation after `Object.assign` | `ownKeys` on a type-driven Proxy lists only the keys already read | `createSpyFromClass(X)` — a real object, with enumerable method keys |
|
|
2358
|
+
| `let s: MockInstance<() => unknown>` not matching anything | `MockInstance<F>` is invariant in `F`; Jest's `SpyInstance` was not | `MockInstance<T['method']>`, or better `injectSpy(X).method` |
|
|
2359
|
+
| two runs with the same totals, one of them missing a suite | a lost `describe` and a fixed flake cancel out in the counters | `compareTestRuns(before, after)` — compare the set of names, not the numbers |
|
|
2360
|
+
| a 30 s timeout, in a different file each run | module-level `vi.fn()` in a fixture shared by files | make the fixture a factory (§10) |
|
|
2361
|
+
| a component's `afterNextRender` state is empty | `detectChanges()` does not run the after-render phase | `await stable(fixture)` (§11) |
|
|
2362
|
+
| a green test whose only line about a call is `spy.m.calledWith(1);` | that is a **stub**, not an assertion — chai's `expect(fn).to.have.been.calledWith(x)` is the one that checks | continue the chain, or `expect(spy.m).toHaveBeenCalledWith(1)` — `no-bare-called-with` (§16) |
|
|
2363
|
+
| `--detect-async-leaks` reporting no leaks in a suite that visibly leaks timers | `setupAutoSpy({ strayTimers: true })` cancelled them before Vitest collected | re-run that file with `strayTimers` off; the warning on stderr says how many were taken (§10) |
|
|
2364
|
+
| `createNestUnit(X): parameter #n of Y is typed as \`Object\``(or`undefined — usually a circular import`) | the compiler emitted no runtime class for that parameter — interface, union, primitive, or a circular import | `@Inject(TOKEN)` plus a `providers` entry, or `@Optional()` to receive `undefined`; `@Inject(forwardRef(() => X))` for the import cycle |
|
|
2365
|
+
| `createNestUnit(X): Y declares n constructor parameter(s) but carries no design:paramtypes metadata` | no `emitDecoratorMetadata` (esbuild / Vite), or `reflect-metadata` loaded after the class | tsc or SWC with `decoratorMetadata`, `@Injectable()`, `reflect-metadata` first; or `@Inject(X)` on every parameter |
|
|
2366
|
+
| `createNestUnit(X): A -> B -> A is a cycle among the classes built for real` | two exposed / `useClass` classes depend on each other; the helper does not resolve `forwardRef` cycles | expose one side less, or provide it |
|
|
2367
|
+
| `createNestUnit(X).spies.get(Y): the unit never asked for that token` | same guard as `createWithAutoSpies` — the spy would not be the one the unit uses | stub the token the unit actually injects (the message lists the auto-spied ones) |
|
|
2368
|
+
| `createNestUnit(X).spies.get(Y): Y is in \`expose\`, so the unit got a real instance` | an exposed class is real, not a spy | read the spies of its own collaborators, or drop it from `expose` |
|
|
2369
|
+
| `no request matched …` (with the requests that were made listed) | the URL, verb or predicate does not describe anything the code under test sent, or nothing subscribed | read the list in the message — it is the requests that were actually made; then fix the URL, add `{ method }`, or subscribe (§13) |
|
|
2370
|
+
| `this TestBed has no HttpTestingController` | `expectRequest` / `expectNoRequest` on a module that never got the testing backend | `TestBed.configureTestingModule({ providers: [...provideHttpTesting()] })` from `vitest-auto-spy/angular-http` (§13) |
|
|
2371
|
+
| `the test ended with N unanswered request(s)` | `provideHttpTesting({ verifyOnTeardown })` found a request the spec never flushed | answer it (`await expectRequest(url).flush(body)`), assert its absence (`expectNoRequest(url)`), or `{ verifyOnTeardown: false }` (§13) |
|
|
2248
2372
|
|
|
2249
2373
|
**Seven of these are library defects, not spec ones.** On anything below 3.8.0, upgrade and change
|
|
2250
2374
|
nothing in the spec: `of(double)` never emitting; `accessorSpies.setters.X` still `undefined` after
|
|
@@ -2518,3 +2642,9 @@ failure with a watchdog. Land the suite green on `subscribeSpyTo`, then move the
|
|
|
2518
2642
|
|
|
2519
2643
|
Full mapping, including what upstream cannot do at all:
|
|
2520
2644
|
<https://asdalexey.github.io/vitest-auto-spy/migrating-jasmine>.
|
|
2645
|
+
|
|
2646
|
+
An Angular suite that came through `ng generate @schematics/angular:refactor-jasmine-vitest` rather
|
|
2647
|
+
than through `jasmine-auto-spies` is a different input — a `MockedObject<T>` literal per double and
|
|
2648
|
+
`// TODO: vitest-migration:` comments on the `createSpyObj` calls it could not expand. That diff is
|
|
2649
|
+
covered at <https://asdalexey.github.io/vitest-auto-spy/migrating-angular-schematic>; the answer to
|
|
2650
|
+
all three TODO categories is `createSpyFromClass(Api)`, which reads the prototype, not the call site.
|