vitest-auto-spy 1.9.2 → 1.10.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/README.md +154 -103
- package/dist/angular.cjs +625 -1
- package/dist/angular.d.cts +54 -6
- package/dist/angular.d.ts +54 -6
- package/dist/angular.js +89 -1
- package/dist/bun.cjs +712 -1
- package/dist/bun.d.cts +2 -2
- package/dist/bun.d.ts +2 -2
- package/dist/bun.js +31 -1
- package/dist/chunk-25A2ZRMV.js +119 -0
- package/dist/chunk-5JKCB5CM.js +22 -0
- package/dist/chunk-IUMGSG5J.js +168 -0
- package/dist/chunk-NQXXUJPS.js +39 -0
- package/dist/chunk-OS2QFTIF.js +35 -0
- package/dist/chunk-T4Q3C2PV.js +354 -0
- package/dist/console.cjs +441 -1
- package/dist/console.d.cts +1 -1
- package/dist/console.d.ts +1 -1
- package/dist/console.js +68 -1
- package/dist/index.cjs +681 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -1
- package/dist/nestjs.cjs +551 -1
- package/dist/nestjs.d.cts +1 -1
- package/dist/nestjs.d.ts +1 -1
- package/dist/nestjs.js +21 -1
- package/dist/node.cjs +713 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +32 -1
- package/dist/react.cjs +681 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +9 -1
- package/dist/rxjs.cjs +63 -1
- package/dist/rxjs.d.cts +2 -2
- package/dist/rxjs.d.ts +2 -2
- package/dist/rxjs.js +162 -1
- package/dist/svelte.cjs +681 -1
- package/dist/svelte.d.cts +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +9 -1
- package/dist/{types-BKS6JdLv.d.cts → types-hTDGBx8V.d.cts} +9 -0
- package/dist/{types-BKS6JdLv.d.ts → types-hTDGBx8V.d.ts} +9 -0
- package/dist/vue.cjs +687 -1
- package/dist/vue.d.cts +2 -2
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +17 -1
- package/package.json +3 -1
- package/dist/chunk-7IUYJ6ZW.js +0 -1
- package/dist/chunk-HDCUMVHT.js +0 -1
- package/dist/chunk-N724W7OZ.js +0 -1
- package/dist/chunk-Q4FBVCJO.js +0 -1
- package/dist/chunk-RTGT5WHR.js +0 -1
- package/dist/chunk-T3N4PY2U.js +0 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
16
16
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
17
17
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
18
18
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
19
|
-
[](#install)
|
|
20
20
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
21
21
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
22
22
|
[](./LICENSE)
|
|
@@ -85,10 +85,10 @@ npm i -D vitest-auto-spy
|
|
|
85
85
|
|
|
86
86
|
### Requirements
|
|
87
87
|
|
|
88
|
-
| Tool
|
|
89
|
-
|
|
|
90
|
-
| Node.js
|
|
91
|
-
| Vitest
|
|
88
|
+
| Tool | Minimum |
|
|
89
|
+
| ---------- | -------------------------------------------------------------- |
|
|
90
|
+
| Node.js | ≥ 18 |
|
|
91
|
+
| Vitest | ≥ 1.0 (required peer) |
|
|
92
92
|
| TypeScript | ≥ 4.7 for the typed helpers (plain JS works too, just untyped) |
|
|
93
93
|
|
|
94
94
|
Ships **dual ESM + CommonJS** with bundled `.d.ts` types, so it drops into both `import`- and
|
|
@@ -99,11 +99,11 @@ Ships **dual ESM + CommonJS** with bundled `.d.ts` types, so it drops into both
|
|
|
99
99
|
All peers are **provided by your project**; `rxjs` and `@angular/core` are **optional** — install
|
|
100
100
|
them only for the matching entry point. The package itself has **zero runtime dependencies**.
|
|
101
101
|
|
|
102
|
-
| Peer
|
|
103
|
-
|
|
|
104
|
-
| `vitest`
|
|
105
|
-
| `rxjs`
|
|
106
|
-
| `@angular/core` | `vitest-auto-spy/angular` helpers
|
|
102
|
+
| Peer | Needed for | Optional? |
|
|
103
|
+
| --------------- | --------------------------------------------------------------------------------------------------------------------------- | --------- |
|
|
104
|
+
| `vitest` | the default runner | no |
|
|
105
|
+
| `rxjs` | `vitest-auto-spy/rxjs` observable spies (and `Spy<T>` type-checking) — `>=7`, **no upper bound** (the rxjs 8 line included) | yes |
|
|
106
|
+
| `@angular/core` | `vitest-auto-spy/angular` helpers | yes |
|
|
107
107
|
|
|
108
108
|
## Availability
|
|
109
109
|
|
|
@@ -111,11 +111,11 @@ them only for the matching entry point. The package itself has **zero runtime de
|
|
|
111
111
|
> and the **Angular / NestJS / React / Vue·Pinia / Svelte** recipes all ship as importable entry points —
|
|
112
112
|
> one identical API across every runner and framework.
|
|
113
113
|
|
|
114
|
-
| Entry point
|
|
115
|
-
|
|
|
114
|
+
| Entry point | Status |
|
|
115
|
+
| ---------------------------------------------------------------------- | ---------------- |
|
|
116
116
|
| `vitest-auto-spy` · `vitest-auto-spy/rxjs` · `vitest-auto-spy/angular` | ✅ **Published** |
|
|
117
|
-
| `vitest-auto-spy/bun` · `vitest-auto-spy/node`
|
|
118
|
-
| `vitest-auto-spy/nestjs` · `/react` · `/vue` · `/svelte` · `/console`
|
|
117
|
+
| `vitest-auto-spy/bun` · `vitest-auto-spy/node` | ✅ **Published** |
|
|
118
|
+
| `vitest-auto-spy/nestjs` · `/react` · `/vue` · `/svelte` · `/console` | ✅ **Published** |
|
|
119
119
|
|
|
120
120
|
## Quick start
|
|
121
121
|
|
|
@@ -124,7 +124,7 @@ effects). The helper you get on each method matches its return type:
|
|
|
124
124
|
|
|
125
125
|
```ts
|
|
126
126
|
import { beforeEach, expect, it } from 'vitest';
|
|
127
|
-
import {
|
|
127
|
+
import { type Spy, createSpyFromClass } from 'vitest-auto-spy';
|
|
128
128
|
|
|
129
129
|
class UserService {
|
|
130
130
|
getName(id: number): string {
|
|
@@ -200,7 +200,7 @@ What it **won't** auto-discover — by design, because these aren't prototype me
|
|
|
200
200
|
mock them by hand. (Same constraint as `jest-auto-spies`.)
|
|
201
201
|
- ⚠️ **Getters / setters** are skipped unless named in `gettersToSpyOn` / `settersToSpyOn` — see
|
|
202
202
|
[Getters & setters](#getters--setters).
|
|
203
|
-
- ⚠️ **Plain data properties** carry no value until you set one; auto-spy mocks
|
|
203
|
+
- ⚠️ **Plain data properties** carry no value until you set one; auto-spy mocks _behaviour_
|
|
204
204
|
(methods), not state. To mock by type including properties, use
|
|
205
205
|
[`createAutoMock`](#auto-mock-by-type-no-class-needed).
|
|
206
206
|
|
|
@@ -209,18 +209,18 @@ What it **won't** auto-discover — by design, because these aren't prototype me
|
|
|
209
209
|
The library ships a framework-agnostic core plus runtime and framework layers, so a plain
|
|
210
210
|
Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
|
|
211
211
|
|
|
212
|
-
| Import
|
|
213
|
-
|
|
|
214
|
-
| `vitest-auto-spy`
|
|
215
|
-
| `vitest-auto-spy/rxjs`
|
|
216
|
-
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `mockReadonlyProp*`, `mockAccessorsProp`
|
|
217
|
-
| `vitest-auto-spy/bun`
|
|
218
|
-
| `vitest-auto-spy/node`
|
|
219
|
-
| `vitest-auto-spy/nestjs`
|
|
220
|
-
| `vitest-auto-spy/react`
|
|
221
|
-
| `vitest-auto-spy/vue`
|
|
222
|
-
| `vitest-auto-spy/svelte`
|
|
223
|
-
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import
|
|
212
|
+
| Import | Provides | Pulls in | Status |
|
|
213
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------- | :----: |
|
|
214
|
+
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
|
|
215
|
+
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) + `createObservableWithValues` | `rxjs` | ✅ |
|
|
216
|
+
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `mockReadonlyProp*`, `mockValueProp`, `mockAccessorsProp`, `restoreMockedProps` | `@angular/core` | ✅ |
|
|
217
|
+
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
218
|
+
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()` | `node:test` | ✅ |
|
|
219
|
+
| `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`) | ✅ |
|
|
220
|
+
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | ✅ |
|
|
221
|
+
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | ✅ |
|
|
222
|
+
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | ✅ |
|
|
223
|
+
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import | `vitest` | ✅ |
|
|
224
224
|
|
|
225
225
|
✅ all entry points published (see [Availability](#availability)).
|
|
226
226
|
|
|
@@ -230,8 +230,9 @@ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runti
|
|
|
230
230
|
|
|
231
231
|
```ts
|
|
232
232
|
import { createSpyFromClass } from 'vitest-auto-spy';
|
|
233
|
-
|
|
234
|
-
import {
|
|
233
|
+
// once (e.g. in your test setup) — enables observable spies
|
|
234
|
+
import { injectSpy, provideAutoSpy } from 'vitest-auto-spy/angular';
|
|
235
|
+
import 'vitest-auto-spy/rxjs';
|
|
235
236
|
```
|
|
236
237
|
|
|
237
238
|
### Runtimes
|
|
@@ -264,14 +265,14 @@ import { createSpyFromClass } from 'vitest-auto-spy/node'; // node:test
|
|
|
264
265
|
|
|
265
266
|
## Comparison
|
|
266
267
|
|
|
267
|
-
| Library
|
|
268
|
-
|
|
|
269
|
-
| **vitest-auto-spy**
|
|
270
|
-
| [jest-auto-spies](https://www.npmjs.com/package/jest-auto-spies)
|
|
271
|
-
| [@bugsplat/vitest-auto-spies](https://www.npmjs.com/package/@bugsplat/vitest-auto-spies) |
|
|
272
|
-
| [vitest-mock-extended](https://www.npmjs.com/package/vitest-mock-extended)
|
|
273
|
-
| [@golevelup/ts-vitest](https://www.npmjs.com/package/@golevelup/ts-vitest)
|
|
274
|
-
| [sinon](https://www.npmjs.com/package/sinon)
|
|
268
|
+
| Library | Reads a class? | Return-type-aware helpers? | Runtimes | We win on |
|
|
269
|
+
| ---------------------------------------------------------------------------------------- | :------------: | :------------------------: | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
270
|
+
| **vitest-auto-spy** | ✅ | ✅ | Vitest · Bun · node:test | — |
|
|
271
|
+
| [jest-auto-spies](https://www.npmjs.com/package/jest-auto-spies) | ✅ | ✅ | Jest only | Vitest/Bun/Node successor, **same API** — direct migration path |
|
|
272
|
+
| [@bugsplat/vitest-auto-spies](https://www.npmjs.com/package/@bugsplat/vitest-auto-spies) | ✅ | ✅ | Vitest only | Same class-based API **plus** Bun & `node:test`, [type-only `createAutoMock`](#auto-mock-by-type-no-class-needed), framework recipes (Angular/NestJS/React/Vue/Svelte), console spies, and **zero runtime deps** (it depends on `@hirez_io/auto-spies-core`) |
|
|
273
|
+
| [vitest-mock-extended](https://www.npmjs.com/package/vitest-mock-extended) | ❌ (Proxy) | ❌ | Vitest | Return-type ergonomics **and** reading a real class (we also ship a Proxy mode: [`createAutoMock`](#auto-mock-by-type-no-class-needed)) |
|
|
274
|
+
| [@golevelup/ts-vitest](https://www.npmjs.com/package/@golevelup/ts-vitest) | partial | ❌ | Vitest | Typed `Promise`/`Observable` helpers + explicit class→spy + `mustBeCalledWith` |
|
|
275
|
+
| [sinon](https://www.npmjs.com/package/sinon) | ❌ (manual) | ❌ | Any | Auto-generated + fully typed vs. manual + loosely typed |
|
|
275
276
|
|
|
276
277
|
**The pitch:** the only auto-spy library that reads a **class** and gives a **fully-typed** spy of
|
|
277
278
|
every method with **return-type-aware** control helpers (`resolveWith` / `nextWith` / `calledWith`) —
|
|
@@ -296,18 +297,18 @@ This also covers migrating from [`@bugsplat/vitest-auto-spies`](https://www.npmj
|
|
|
296
297
|
which re-exports the same `jest-auto-spies` API — the swap is identical, and you gain Bun /
|
|
297
298
|
`node:test`, `createAutoMock`, framework recipes and console spies on top.
|
|
298
299
|
|
|
299
|
-
| jest-auto-spies
|
|
300
|
-
|
|
|
301
|
-
| `createSpyFromClass`
|
|
302
|
-
| `provideAutoSpy`
|
|
303
|
-
| `calledWith` / `mustBeCalledWith`
|
|
304
|
-
| `calledWith(...).returnValue(v)`
|
|
305
|
-
| `resolveWith` / `rejectWith` / `resolveWithPerCall`
|
|
306
|
-
| `nextWith` / `nextOneTimeWith` / `nextWithValues` / `nextWithPerCall` | same
|
|
307
|
-
| `throwWith` / `complete` / `returnSubject`
|
|
308
|
-
| `accessorSpies.getters/setters`
|
|
309
|
-
| `createObservableWithValues`
|
|
310
|
-
| underlying mock
|
|
300
|
+
| jest-auto-spies | vitest-auto-spy | Status |
|
|
301
|
+
| --------------------------------------------------------------------- | ---------------------------------------------------------- | ------------ |
|
|
302
|
+
| `createSpyFromClass` | `createSpyFromClass` | ✅ identical |
|
|
303
|
+
| `provideAutoSpy` | `provideAutoSpy` | ✅ identical |
|
|
304
|
+
| `calledWith` / `mustBeCalledWith` | same | ✅ identical |
|
|
305
|
+
| `calledWith(...).returnValue(v)` | same — `.returnValue` **and** `.mockReturnValue` both work | ✅ identical |
|
|
306
|
+
| `resolveWith` / `rejectWith` / `resolveWithPerCall` | same | ✅ identical |
|
|
307
|
+
| `nextWith` / `nextOneTimeWith` / `nextWithValues` / `nextWithPerCall` | same | ✅ identical |
|
|
308
|
+
| `throwWith` / `complete` / `returnSubject` | same | ✅ identical |
|
|
309
|
+
| `accessorSpies.getters/setters` | same | ✅ identical |
|
|
310
|
+
| `createObservableWithValues` | same | ✅ identical |
|
|
311
|
+
| underlying mock | `jest.fn()` → `vi.fn()` | 🔁 swapped |
|
|
311
312
|
|
|
312
313
|
Just make sure your tests run under Vitest, and (for Angular) that `TestBed` is set up.
|
|
313
314
|
|
|
@@ -323,12 +324,36 @@ createSpyFromClass(MyService, ['getName', 'getAge']);
|
|
|
323
324
|
// 3. full config object
|
|
324
325
|
createSpyFromClass(MyService, {
|
|
325
326
|
methodsToSpyOn: ['getName'],
|
|
327
|
+
instanceMethodsToSpyOn: ['count'], // callables that live on the instance, not the prototype
|
|
326
328
|
observablePropsToSpyOn: ['products$'], // Observable *properties*
|
|
327
329
|
gettersToSpyOn: ['userName'],
|
|
328
330
|
settersToSpyOn: ['userName'],
|
|
329
331
|
});
|
|
330
332
|
```
|
|
331
333
|
|
|
334
|
+
### Spying instance-assigned callables (`signal()`, arrow props, `signalStore()`)
|
|
335
|
+
|
|
336
|
+
`createSpyFromClass` discovers methods by walking the **prototype**, so a callable assigned on the
|
|
337
|
+
instance is invisible to it — an arrow-function property, an Angular `signal()` / `computed()`
|
|
338
|
+
field, a method of an ngrx `signalStore()`. Listing such a name in `methodsToSpyOn` is not the
|
|
339
|
+
answer either: that option _restricts_ what gets spied, and the name is reported as a probable typo.
|
|
340
|
+
|
|
341
|
+
Name them in `instanceMethodsToSpyOn` instead. They are **added** to whatever the method resolution
|
|
342
|
+
produced, and never warn:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
class SettingsService {
|
|
346
|
+
readonly isReady = signal(false); // instance field — not on the prototype
|
|
347
|
+
load(): void {} // prototype method — auto-discovered
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const spy = createSpyFromClass(SettingsService, { instanceMethodsToSpyOn: ['isReady'] });
|
|
351
|
+
|
|
352
|
+
spy.isReady.mockReturnValue(true);
|
|
353
|
+
expect(spy.isReady()).toBe(true);
|
|
354
|
+
expect(vi.isMockFunction(spy.load)).toBe(true); // still spied
|
|
355
|
+
```
|
|
356
|
+
|
|
332
357
|
## Auto-mock by type (no class needed)
|
|
333
358
|
|
|
334
359
|
`createSpyFromClass` reads a real class's prototype. When you only have a TypeScript **interface or
|
|
@@ -355,8 +380,8 @@ Every accessed method becomes a decorated spy with the **same typed control help
|
|
|
355
380
|
`createSpyFromClass`, materialized lazily and cached (same reference on re-access):
|
|
356
381
|
|
|
357
382
|
```ts
|
|
358
|
-
svc.getName.calledWith(1).mockReturnValue('Ada');
|
|
359
|
-
svc.getUser.resolveWith({ id: 1, name: 'Ada' });
|
|
383
|
+
svc.getName.calledWith(1).mockReturnValue('Ada'); // sync, arg-matched
|
|
384
|
+
svc.getUser.resolveWith({ id: 1, name: 'Ada' }); // promise helper
|
|
360
385
|
expect(svc.getName(1)).toBe('Ada');
|
|
361
386
|
await expect(svc.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
|
|
362
387
|
```
|
|
@@ -414,16 +439,12 @@ import 'vitest-auto-spy/rxjs';
|
|
|
414
439
|
|
|
415
440
|
```ts
|
|
416
441
|
myService.getProducts$.nextWith([{ name: 'Product 1' }]); // emit, stream stays open
|
|
417
|
-
myService.getProducts$.nextOneTimeWith([{ name: 'X' }]);
|
|
418
|
-
myService.getProducts$.throwWith('FAKE ERROR');
|
|
419
|
-
myService.getProducts$.complete();
|
|
442
|
+
myService.getProducts$.nextOneTimeWith([{ name: 'X' }]); // emit one value, then complete
|
|
443
|
+
myService.getProducts$.throwWith('FAKE ERROR'); // error the stream
|
|
444
|
+
myService.getProducts$.complete(); // complete the stream
|
|
420
445
|
|
|
421
446
|
// emit a precise sequence — values, errors, completion, optional delays
|
|
422
|
-
myService.getProducts$.nextWithValues([
|
|
423
|
-
{ value: [{ name: 'Product 1' }] },
|
|
424
|
-
{ errorValue: 'FAKE ERROR' },
|
|
425
|
-
{ complete: true },
|
|
426
|
-
]);
|
|
447
|
+
myService.getProducts$.nextWithValues([{ value: [{ name: 'Product 1' }] }, { errorValue: 'FAKE ERROR' }, { complete: true }]);
|
|
427
448
|
|
|
428
449
|
// a fresh stream per call
|
|
429
450
|
myService.getProducts$.nextWithPerCall([{ value: ['a'] }, { value: ['b'] }]);
|
|
@@ -485,8 +506,8 @@ pull it back out already typed as `Spy<T>`. `@nestjs/common` / `@nestjs/testing`
|
|
|
485
506
|
|
|
486
507
|
```ts
|
|
487
508
|
import { Test, type TestingModule } from '@nestjs/testing';
|
|
488
|
-
import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/nestjs';
|
|
489
509
|
import { beforeEach, expect, it } from 'vitest';
|
|
510
|
+
import { injectSpy, provideAutoSpy } from 'vitest-auto-spy/nestjs';
|
|
490
511
|
|
|
491
512
|
import { AuthService } from './auth.service';
|
|
492
513
|
import { UserService } from './user.service';
|
|
@@ -558,24 +579,22 @@ it('drives async deps and asserts the component called them', async () => {
|
|
|
558
579
|
`@vue/test-utils`' `global.provide`; for a class-based Pinia store, spy it directly:
|
|
559
580
|
|
|
560
581
|
```ts
|
|
582
|
+
// (a) class-based service injected via provide / global.provide
|
|
583
|
+
import { UserService, UserServiceKey } from '@/services/user.service';
|
|
584
|
+
// (b) class-based Pinia store — every action becomes a spy
|
|
585
|
+
import { CartStore } from '@/stores/cart.store';
|
|
561
586
|
import { mount } from '@vue/test-utils';
|
|
562
587
|
import { createSpyFromClass, provideAutoSpy } from 'vitest-auto-spy/vue';
|
|
563
588
|
|
|
564
|
-
// (a) class-based service injected via provide / global.provide
|
|
565
|
-
import { UserServiceKey, UserService } from '@/services/user.service';
|
|
566
|
-
|
|
567
589
|
const provide = provideAutoSpy(UserServiceKey, UserService); // { [UserServiceKey]: Spy<UserService> }
|
|
568
590
|
provide[UserServiceKey].getName.mockReturnValue('Fake Name');
|
|
569
591
|
|
|
570
592
|
const wrapper = mount(UserBadge, { global: { provide } });
|
|
571
593
|
expect(provide[UserServiceKey].getName).toHaveBeenCalled();
|
|
572
594
|
|
|
573
|
-
// (b) class-based Pinia store — every action becomes a spy
|
|
574
|
-
import { CartStore } from '@/stores/cart.store';
|
|
575
|
-
|
|
576
595
|
const store = createSpyFromClass(CartStore);
|
|
577
|
-
store.itemCount.mockReturnValue(3);
|
|
578
|
-
store.checkout.resolveWith({ orderId: 'ord_42' });
|
|
596
|
+
store.itemCount.mockReturnValue(3); // sync action/getter
|
|
597
|
+
store.checkout.resolveWith({ orderId: 'ord_42' }); // async action (Promise)
|
|
579
598
|
await store.checkout('tok_abc');
|
|
580
599
|
expect(store.checkout).toHaveBeenCalledWith('tok_abc');
|
|
581
600
|
```
|
|
@@ -589,6 +608,7 @@ one (props, context, or a mocked module):
|
|
|
589
608
|
```ts
|
|
590
609
|
import { render } from '@testing-library/svelte';
|
|
591
610
|
import { createSpyFromClass } from 'vitest-auto-spy/svelte';
|
|
611
|
+
|
|
592
612
|
import Cart from './Cart.svelte';
|
|
593
613
|
import { CartStore } from './cart-store';
|
|
594
614
|
|
|
@@ -615,7 +635,7 @@ it('shows the cart total from the store', () => {
|
|
|
615
635
|
`provideAutoSpy` is the shorthand for providing an auto-spy in a `TestBed`:
|
|
616
636
|
|
|
617
637
|
```ts
|
|
618
|
-
import {
|
|
638
|
+
import { injectSpy, provideAutoSpy } from 'vitest-auto-spy/angular';
|
|
619
639
|
|
|
620
640
|
TestBed.configureTestingModule({
|
|
621
641
|
providers: [
|
|
@@ -647,35 +667,64 @@ beforeEach(() => {
|
|
|
647
667
|
#### Signal / readonly property mocking (bonus)
|
|
648
668
|
|
|
649
669
|
```ts
|
|
650
|
-
import { mockReadonlyProp, mockReadonlyPropGetter,
|
|
670
|
+
import { mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, restoreMockedProps } from 'vitest-auto-spy/angular';
|
|
651
671
|
|
|
652
|
-
mockReadonlyProp(service, 'isReady', true);
|
|
653
|
-
mockReadonlyPropGetter(service, 'label', () => 'A');
|
|
654
|
-
|
|
672
|
+
mockReadonlyProp(service, 'isReady', true); // static value (incl. signals)
|
|
673
|
+
mockReadonlyPropGetter(service, 'label', () => 'A'); // dynamic getter
|
|
674
|
+
mockValueProp(service, 'retries', 3); // plain writable value
|
|
675
|
+
mockAccessorsProp(service, 'theme'); // spied get + set
|
|
676
|
+
mockAccessorsProp(input, 'valueAsNumber', { get, set }); // …with real implementations behind them
|
|
655
677
|
```
|
|
656
678
|
|
|
679
|
+
Each helper also returns the undo for _its own_ patch, for a stub that has to come off inside a
|
|
680
|
+
single test:
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
const restoreNavigator = mockValueProp(globalThis, 'navigator', undefined);
|
|
684
|
+
|
|
685
|
+
try {
|
|
686
|
+
// …
|
|
687
|
+
} finally {
|
|
688
|
+
restoreNavigator();
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Every one of them records the descriptor it overwrote, so a single `restoreMockedProps()` puts them
|
|
693
|
+
all back. That matters when the patched object outlives the spec file — a global, a class
|
|
694
|
+
prototype, a singleton — which is always the case under Vitest's `isolate: false`:
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
// test setup
|
|
698
|
+
afterEach(() => restoreMockedProps());
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Members the public type does not describe (`#private` fields, ad-hoc keys) are accepted too — the
|
|
702
|
+
helpers take a `PropertyKey` overload, so no `as never` dance.
|
|
703
|
+
|
|
657
704
|
## Utilities
|
|
658
705
|
|
|
659
706
|
Beyond the spy factories, the package ships a set of small standalone helpers. Each one is a
|
|
660
707
|
single-purpose utility you can pick up independently — they all ride on the same core:
|
|
661
708
|
|
|
662
|
-
| Utility
|
|
663
|
-
|
|
|
664
|
-
| `injectSpy(token)` / `injectSpy(moduleRef, token)` | `/angular`, `/nestjs`
|
|
665
|
-
| `provideAutoSpy(Class, config?)`
|
|
666
|
-
| `createFunctionSpy(name)`
|
|
667
|
-
| `createAutoMock<T>(overrides?)`
|
|
668
|
-
| `createObservableWithValues(configs, opts?)`
|
|
669
|
-
| `consoleInfoSpy` / `consoleWarnSpy` / …
|
|
670
|
-
| `mockReadonlyProp(obj, prop, value)`
|
|
671
|
-
| `mockReadonlyPropGetter(obj, prop, getter)`
|
|
672
|
-
| `
|
|
673
|
-
| `
|
|
709
|
+
| Utility | Entry point | What it's for |
|
|
710
|
+
| -------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
711
|
+
| `injectSpy(token)` / `injectSpy(moduleRef, token)` | `/angular`, `/nestjs` | Pull a provided spy out of the DI container, already typed as `Spy<T>` — no casting |
|
|
712
|
+
| `provideAutoSpy(Class, config?)` | `/angular`, `/nestjs`, `/vue` | One-liner `{ provide, useValue }` (or Vue `global.provide`) that builds the spy for you |
|
|
713
|
+
| `createFunctionSpy(name)` | core | A single standalone function spy with the full helper set (`calledWith`, `resolveWith`, `nextWith`, …) — no class needed |
|
|
714
|
+
| `createAutoMock<T>(overrides?)` | core | Proxy-based spy from a **type/interface** alone ([details](#auto-mock-by-type-no-class-needed)) |
|
|
715
|
+
| `createObservableWithValues(configs, opts?)` | `/rxjs` | Build a fake `Observable` emitting a precise sequence of values / errors / completion |
|
|
716
|
+
| `consoleInfoSpy` / `consoleWarnSpy` / … | `/console` | Silent typed spies over the global `console`, installed on import ([details](#console-spies--vitest-auto-spyconsole)) |
|
|
717
|
+
| `mockReadonlyProp(obj, prop, value)` | `/angular` | Overwrite a `readonly` property (incl. Angular signals) with a static value |
|
|
718
|
+
| `mockReadonlyPropGetter(obj, prop, getter)` | `/angular` | Same, but backed by a dynamic getter |
|
|
719
|
+
| `mockValueProp(obj, prop, value)` | `/angular` | Overwrite a property with a plain **writable** value |
|
|
720
|
+
| `mockAccessorsProp(obj, prop, accessors?)` | `/angular` | Redefine a property with spied `get` + `set`, optionally backed by real implementations |
|
|
721
|
+
| `restoreMockedProps()` | `/angular` | Undo every patch the `mock*Prop` helpers applied — one call in `afterEach` (each helper also returns the undo for its own patch) |
|
|
722
|
+
| `errorHandler` | core | The `mustBeCalledWith` argument-mismatch reporter — swap it to customize failure output |
|
|
674
723
|
|
|
675
724
|
A taste of the DI pair — provide the spy, inject it back fully typed:
|
|
676
725
|
|
|
677
726
|
```ts
|
|
678
|
-
import {
|
|
727
|
+
import { injectSpy, provideAutoSpy } from 'vitest-auto-spy/angular';
|
|
679
728
|
|
|
680
729
|
TestBed.configureTestingModule({ providers: [provideAutoSpy(UserService)] });
|
|
681
730
|
const userService = injectSpy(UserService); // Spy<UserService>, no `as` cast
|
|
@@ -729,21 +778,22 @@ expect(fakeConsole.info).toHaveBeenCalledWith('done');
|
|
|
729
778
|
|
|
730
779
|
## API reference
|
|
731
780
|
|
|
732
|
-
| Export
|
|
733
|
-
|
|
|
734
|
-
| `createSpyFromClass(Class, methodsOrConfig?)`
|
|
735
|
-
| `createAutoMock<T>(overrides?)`
|
|
736
|
-
| `mockDeep<T>(overrides?)`
|
|
737
|
-
| `resetAutoSpy(spy)` / `clearAutoSpy(spy)`
|
|
738
|
-
| `provideAutoSpy(Class, methodsOrConfig?)`
|
|
739
|
-
| `provideAutoSpy(token, Class, methodsOrConfig?)`
|
|
740
|
-
| `injectSpy(token)` _(Angular)_ / `injectSpy(moduleRef, token)` _(NestJS)_
|
|
741
|
-
| `createFunctionSpy(name)`
|
|
742
|
-
| `createObservableWithValues(configs, opts?)`
|
|
743
|
-
| `mockReadonlyProp` / `mockReadonlyPropGetter` / `mockAccessorsProp` | Mock readonly / accessor / signal props
|
|
744
|
-
| `
|
|
745
|
-
| `
|
|
746
|
-
| `
|
|
781
|
+
| Export | Description |
|
|
782
|
+
| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
783
|
+
| `createSpyFromClass(Class, methodsOrConfig?)` | Build a fully-typed `Spy<T>` from a class |
|
|
784
|
+
| `createAutoMock<T>(overrides?)` | Build a `Spy<T>` from a **type/interface** alone (Proxy, no class) |
|
|
785
|
+
| `mockDeep<T>(overrides?)` | Build a **recursive** auto-mock — `mock.repo.user.find()` chains without seeding |
|
|
786
|
+
| `resetAutoSpy(spy)` / `clearAutoSpy(spy)` | Reset every spy in an auto-spy at once — `reset` also reverts return-value config (`calledWith` **and** a bare `mockReturnValue`); `clear` keeps it |
|
|
787
|
+
| `provideAutoSpy(Class, methodsOrConfig?)` | Angular / NestJS `{ provide, useValue }` shorthand |
|
|
788
|
+
| `provideAutoSpy(token, Class, methodsOrConfig?)` | Vue `{ [token]: Spy<T> }` for `global.provide` |
|
|
789
|
+
| `injectSpy(token)` _(Angular)_ / `injectSpy(moduleRef, token)` _(NestJS)_ | Inject typed as `Spy<T>` |
|
|
790
|
+
| `createFunctionSpy(name)` | A single standalone function spy with all helpers |
|
|
791
|
+
| `createObservableWithValues(configs, opts?)` | Build an Observable from value configs |
|
|
792
|
+
| `mockReadonlyProp` / `mockReadonlyPropGetter` / `mockValueProp` / `mockAccessorsProp` | Mock readonly / writable / accessor / signal props |
|
|
793
|
+
| `restoreMockedProps()` | Undo every `mock*Prop` patch (descriptors restored newest-first) |
|
|
794
|
+
| `consoleDebugSpy` … `consoleWarnSpy` _(`/console`)_ | Silent typed spies replacing the global `console` methods on import |
|
|
795
|
+
| `installConsoleSpies()` / `resetConsoleSpies()` / `restoreConsole()` | Install / clear / undo the console spies |
|
|
796
|
+
| `errorHandler` | The `mustBeCalledWith` argument-mismatch error helper |
|
|
747
797
|
|
|
748
798
|
**Spied sync method:** `mockReturnValue`, `calledWith(...)`, `mustBeCalledWith(...)` — `calledWith`
|
|
749
799
|
also matches **asymmetric matchers** (`calledWith(expect.any(Number))`, `expect.objectContaining({...})`)
|
|
@@ -753,7 +803,8 @@ also matches **asymmetric matchers** (`calledWith(expect.any(Number))`, `expect.
|
|
|
753
803
|
**Spied Observable method / property:** `nextWith`, `nextOneTimeWith`, `nextWithValues`,
|
|
754
804
|
`nextWithPerCall`, `throwWith`, `complete`, `returnSubject`
|
|
755
805
|
|
|
756
|
-
**Config (`ClassSpyConfiguration`):** `methodsToSpyOn`, `
|
|
806
|
+
**Config (`ClassSpyConfiguration`):** `methodsToSpyOn`, `instanceMethodsToSpyOn` (callables that
|
|
807
|
+
live on the instance — `signal()` fields, arrow props, `signalStore()` methods), `observablePropsToSpyOn`,
|
|
757
808
|
`gettersToSpyOn`, `settersToSpyOn`, `autoSpyAccessors` (discover every getter/setter),
|
|
758
809
|
`lazySpies` (materialize method spies on first access — cheaper for wide classes; the `provideAutoSpy` default on Angular)
|
|
759
810
|
|