vitest-auto-spy 1.2.0 → 1.4.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 +415 -32
- package/dist/angular.cjs +1 -1
- package/dist/angular.d.cts +2 -2
- package/dist/angular.d.ts +2 -2
- package/dist/angular.js +1 -1
- package/dist/bun.cjs +1 -0
- package/dist/bun.d.cts +27 -0
- package/dist/bun.d.ts +27 -0
- package/dist/bun.js +1 -0
- package/dist/chunk-6LFQMHSX.js +1 -0
- package/dist/chunk-ACSYU277.js +1 -0
- package/dist/chunk-IPBQZ4JC.js +1 -0
- package/dist/chunk-VLNCYBUN.js +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -14
- package/dist/index.d.ts +2 -14
- package/dist/index.js +1 -1
- package/dist/nestjs.cjs +1 -0
- package/dist/nestjs.d.cts +23 -0
- package/dist/nestjs.d.ts +23 -0
- package/dist/nestjs.js +1 -0
- package/dist/node.cjs +1 -0
- package/dist/node.d.cts +4 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.js +1 -0
- package/dist/react.cjs +1 -0
- package/dist/react.d.cts +4 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +1 -0
- package/dist/rxjs.d.cts +2 -2
- package/dist/rxjs.d.ts +2 -2
- package/dist/svelte.cjs +1 -0
- package/dist/svelte.d.cts +4 -0
- package/dist/svelte.d.ts +4 -0
- package/dist/svelte.js +1 -0
- package/dist/{types-Cg9eZmvm.d.cts → types-C84-zP_V.d.cts} +1 -1
- package/dist/{types-Cg9eZmvm.d.ts → types-C84-zP_V.d.ts} +1 -1
- package/dist/vue.cjs +1 -0
- package/dist/vue.d.cts +23 -0
- package/dist/vue.d.ts +23 -0
- package/dist/vue.js +1 -0
- package/package.json +63 -5
- package/dist/chunk-6S2BI3IA.js +0 -1
package/README.md
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
# vitest-auto-spy
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Auto-generate fully-typed test spies from a class — across any Vitest-compatible runtime and framework.**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
The only auto-spy library that reads a **class** and gives a **fully-typed** spy of every method
|
|
8
|
+
with **return-type-aware** helpers (`resolveWith` / `nextWith` / `calledWith`). Available today for
|
|
9
|
+
**Vitest** (with **RxJS** and **Angular** helpers); **Bun**, **node:test**, and NestJS / React /
|
|
10
|
+
Vue·Pinia / Svelte recipes land in the next release ([availability](#availability)). A drop-in
|
|
11
|
+
replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — same API.
|
|
9
12
|
|
|
10
13
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
11
14
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
@@ -15,33 +18,53 @@ same API, but spying only on **Vitest** instead of Jest.
|
|
|
15
18
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
16
19
|
[](./LICENSE)
|
|
17
20
|
|
|
21
|
+
[](#runtimes)
|
|
22
|
+
[](#availability)
|
|
23
|
+
[](#availability)
|
|
24
|
+
[](#install)
|
|
25
|
+
|
|
18
26
|
📦 [**npm**](https://www.npmjs.com/package/vitest-auto-spy) · 🐙 [**GitHub**](https://github.com/ASDAlexey/vitest-auto-spy) · 🔖 [**Changelog**](./CHANGELOG.md)
|
|
19
27
|
|
|
20
28
|
</div>
|
|
21
29
|
|
|
22
30
|
---
|
|
23
31
|
|
|
24
|
-
- 🧪 Reads a class and generates a typed
|
|
32
|
+
- 🧪 Reads a class and generates a typed spy for **every** method — no hand-written `vi.fn()` lists
|
|
33
|
+
- 🧬 Or mock from a **type/interface** alone — `createAutoMock<T>()`, no class required
|
|
34
|
+
- 🌐 One `MockAdapter` core — **Vitest** today; **Bun** and **`node:test`** next release, identical API on each
|
|
35
|
+
- 🧩 Framework recipes: **Angular** today; **NestJS, React, Vue/Pinia, Svelte** next release
|
|
25
36
|
- 🎯 Return-type-aware helpers — sync, `Promise`, and `Observable` all get the right API
|
|
26
37
|
- 🔀 `calledWith` / `mustBeCalledWith` argument dispatch
|
|
27
38
|
- 📡 First-class RxJS `Observable` spying (`nextWith`, `nextWithValues`, `throwWith`, …)
|
|
28
39
|
- ⚙️ Getter / setter spies via `accessorSpies`
|
|
29
|
-
-
|
|
30
|
-
- 🟢 100% test coverage, zero runtime dependencies beyond a tiny arg serializer
|
|
40
|
+
- 🟢 100% test coverage, **zero runtime dependencies** (in-tree arg serializer, no `javascript-stringify`)
|
|
31
41
|
|
|
32
42
|
## Table of contents
|
|
33
43
|
|
|
34
44
|
- [Install](#install)
|
|
45
|
+
- [Availability](#availability)
|
|
46
|
+
- [Quick start](#quick-start)
|
|
35
47
|
- [Why](#why)
|
|
48
|
+
- [How it works (and what it won't spy)](#how-it-works-and-what-it-wont-spy)
|
|
49
|
+
- [Comparison](#comparison)
|
|
36
50
|
- [Migrating from jest-auto-spies](#migrating-from-jest-auto-spies)
|
|
37
51
|
- [Configuration](#configuration)
|
|
52
|
+
- [Auto-mock by type (no class needed)](#auto-mock-by-type-no-class-needed)
|
|
38
53
|
- [Synchronous methods](#synchronous-methods)
|
|
39
54
|
- [Promise-returning methods](#promise-returning-methods)
|
|
40
55
|
- [Observable methods & properties](#observable-returning-methods--observable-properties)
|
|
41
56
|
- [Getters & setters](#getters--setters)
|
|
42
|
-
- [
|
|
57
|
+
- [Framework adapters](#framework-adapters)
|
|
58
|
+
- [NestJS](#nestjs)
|
|
59
|
+
- [React (Testing Library)](#react-testing-library)
|
|
60
|
+
- [Vue / Pinia](#vue--pinia)
|
|
61
|
+
- [Svelte](#svelte)
|
|
62
|
+
- [Angular](#angular)
|
|
43
63
|
- [API reference](#api-reference)
|
|
64
|
+
- [FAQ & troubleshooting](#faq--troubleshooting)
|
|
65
|
+
- [Versioning](#versioning)
|
|
44
66
|
- [Contributing](#contributing)
|
|
67
|
+
- [Acknowledgements](#acknowledgements)
|
|
45
68
|
- [License](#license)
|
|
46
69
|
|
|
47
70
|
## Install
|
|
@@ -50,33 +73,77 @@ same API, but spying only on **Vitest** instead of Jest.
|
|
|
50
73
|
npm i -D vitest-auto-spy
|
|
51
74
|
```
|
|
52
75
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
### Requirements
|
|
77
|
+
|
|
78
|
+
| Tool | Minimum |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| Node.js | ≥ 18 |
|
|
81
|
+
| Vitest | ≥ 1.0 (required peer) |
|
|
82
|
+
| TypeScript | ≥ 4.7 for the typed helpers (plain JS works too, just untyped) |
|
|
56
83
|
|
|
57
|
-
|
|
84
|
+
Ships **dual ESM + CommonJS** with bundled `.d.ts` types, so it drops into both `import`- and
|
|
85
|
+
`require`-style test setups.
|
|
58
86
|
|
|
59
|
-
|
|
60
|
-
React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
|
|
87
|
+
### Peer dependencies
|
|
61
88
|
|
|
62
|
-
|
|
89
|
+
All peers are **provided by your project**; `rxjs` and `@angular/core` are **optional** — install
|
|
90
|
+
them only for the matching entry point. The package itself has **zero runtime dependencies**.
|
|
91
|
+
|
|
92
|
+
| Peer | Needed for | Optional? |
|
|
63
93
|
| --- | --- | --- |
|
|
64
|
-
| `vitest
|
|
65
|
-
| `vitest-auto-spy/rxjs`
|
|
66
|
-
| `vitest-auto-spy/angular`
|
|
94
|
+
| `vitest` | the default runner | no |
|
|
95
|
+
| `rxjs` | `vitest-auto-spy/rxjs` observable spies (and `Spy<T>` type-checking) | yes |
|
|
96
|
+
| `@angular/core` | `vitest-auto-spy/angular` helpers | yes |
|
|
97
|
+
|
|
98
|
+
## Availability
|
|
99
|
+
|
|
100
|
+
> **On npm today vs. shipping next.** `vitest-auto-spy@1.3.0` publishes the **Vitest core** plus the
|
|
101
|
+
> **RxJS** and **Angular** layers. The Bun / `node:test` runtimes and the NestJS / React / Vue /
|
|
102
|
+
> Svelte recipes are implemented and tested in the repo but **not yet published as entry points** —
|
|
103
|
+
> they land in the next release. Snippets that use them are marked _next release_ below.
|
|
104
|
+
|
|
105
|
+
| Entry point | Status |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `vitest-auto-spy` · `vitest-auto-spy/rxjs` · `vitest-auto-spy/angular` | ✅ **Published** (1.3.0) |
|
|
108
|
+
| `vitest-auto-spy/bun` · `vitest-auto-spy/node` | 🔜 next release |
|
|
109
|
+
| `vitest-auto-spy/nestjs` · `/react` · `/vue` · `/svelte` | 🔜 next release |
|
|
110
|
+
|
|
111
|
+
## Quick start
|
|
112
|
+
|
|
113
|
+
Pass a class — every method becomes a typed spy, and the **constructor is never called** (no side
|
|
114
|
+
effects). The helper you get on each method matches its return type:
|
|
67
115
|
|
|
68
116
|
```ts
|
|
69
|
-
import {
|
|
70
|
-
import 'vitest-auto-spy
|
|
71
|
-
|
|
117
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
118
|
+
import { createSpyFromClass, type Spy } from 'vitest-auto-spy';
|
|
119
|
+
|
|
120
|
+
class UserService {
|
|
121
|
+
getName(id: number): string {
|
|
122
|
+
return 'real name';
|
|
123
|
+
}
|
|
124
|
+
async getUser(id: number): Promise<{ id: number; name: string }> {
|
|
125
|
+
return fetchUser(id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let userService: Spy<UserService>;
|
|
130
|
+
|
|
131
|
+
beforeEach(() => {
|
|
132
|
+
userService = createSpyFromClass(UserService); // every method is now a spy
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('stubs each method with the right helper for its return type', async () => {
|
|
136
|
+
userService.getName.mockReturnValue('Ada'); // sync
|
|
137
|
+
userService.getUser.resolveWith({ id: 1, name: 'Ada' }); // Promise helper
|
|
138
|
+
|
|
139
|
+
expect(userService.getName(1)).toBe('Ada');
|
|
140
|
+
await expect(userService.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
|
|
141
|
+
expect(userService.getName).toHaveBeenCalledWith(1);
|
|
142
|
+
});
|
|
72
143
|
```
|
|
73
144
|
|
|
74
|
-
|
|
75
|
-
>
|
|
76
|
-
>
|
|
77
|
-
> The decoupling is at the **runtime** level. The core's _type_ surface (`Spy<T>`) still
|
|
78
|
-
> references rxjs types, so keep `rxjs` available for type-checking (it's normally already a
|
|
79
|
-
> devDependency); none of it reaches your runtime bundle.
|
|
145
|
+
No class, only a TypeScript type? Reach for
|
|
146
|
+
[`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed).
|
|
80
147
|
|
|
81
148
|
## Why
|
|
82
149
|
|
|
@@ -105,6 +172,100 @@ beforeEach(() => {
|
|
|
105
172
|
`Spy<UserService>` exposes each method as a `vi.fn()` **plus** the right helpers based on
|
|
106
173
|
the method's return type (sync / `Promise` / `Observable`).
|
|
107
174
|
|
|
175
|
+
## How it works (and what it won't spy)
|
|
176
|
+
|
|
177
|
+
`createSpyFromClass(MyService)` reads `MyService.prototype` and walks the **prototype chain** — it
|
|
178
|
+
never `new`s the class. Concretely:
|
|
179
|
+
|
|
180
|
+
- ✅ **The class is never instantiated.** The constructor and its side effects (HTTP clients, DB
|
|
181
|
+
connections, `inject()` calls) never run — you pass the class itself, not an instance.
|
|
182
|
+
- ✅ **Inherited methods are spied too**, all the way up the prototype chain.
|
|
183
|
+
- ✅ Each method is replaced by a fresh spy carrying the helpers that match its **return type**:
|
|
184
|
+
sync → `mockReturnValue` / `calledWith`; `Promise` → `resolveWith` / `rejectWith`; `Observable`
|
|
185
|
+
→ `nextWith` / `throwWith` / … .
|
|
186
|
+
|
|
187
|
+
What it **won't** auto-discover — by design, because these aren't prototype methods:
|
|
188
|
+
|
|
189
|
+
- ⚠️ **Arrow-function class fields** (`doThing = () => {}`) are instance properties set in the
|
|
190
|
+
constructor, so prototype scanning can't see them. Use regular methods, list them explicitly, or
|
|
191
|
+
mock them by hand. (Same constraint as `jest-auto-spies`.)
|
|
192
|
+
- ⚠️ **Getters / setters** are skipped unless named in `gettersToSpyOn` / `settersToSpyOn` — see
|
|
193
|
+
[Getters & setters](#getters--setters).
|
|
194
|
+
- ⚠️ **Plain data properties** carry no value until you set one; auto-spy mocks *behaviour*
|
|
195
|
+
(methods), not state. To mock by type including properties, use
|
|
196
|
+
[`createAutoMock`](#auto-mock-by-type-no-class-needed).
|
|
197
|
+
|
|
198
|
+
## Entry points & runtimes
|
|
199
|
+
|
|
200
|
+
The library ships a framework-agnostic core plus runtime and framework layers, so a plain
|
|
201
|
+
Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
|
|
202
|
+
|
|
203
|
+
| Import | Provides | Pulls in | Status |
|
|
204
|
+
| --- | --- | --- | :---: |
|
|
205
|
+
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
|
|
206
|
+
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) + `createObservableWithValues` | `rxjs` | ✅ |
|
|
207
|
+
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `mockReadonlyProp*`, `mockAccessorsProp` | `@angular/core` | ✅ |
|
|
208
|
+
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | 🔜 |
|
|
209
|
+
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()` | `node:test` | 🔜 |
|
|
210
|
+
| `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`) | 🔜 |
|
|
211
|
+
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | 🔜 |
|
|
212
|
+
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | 🔜 |
|
|
213
|
+
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | 🔜 |
|
|
214
|
+
|
|
215
|
+
✅ published in 1.3.0 · 🔜 next release (see [Availability](#availability)).
|
|
216
|
+
|
|
217
|
+
> The framework subpaths import **nothing** from their framework — the helpers are structural, so
|
|
218
|
+
> `@nestjs/*`, `react`, `vue`/`pinia` and `svelte` stay your own (already-present) dev dependencies and
|
|
219
|
+
> never reach this package's runtime bundle.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { createSpyFromClass } from 'vitest-auto-spy';
|
|
223
|
+
import 'vitest-auto-spy/rxjs'; // once (e.g. in your test setup) — enables observable spies
|
|
224
|
+
import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/angular';
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Runtimes
|
|
228
|
+
|
|
229
|
+
The core is runner-agnostic behind a `MockAdapter`: pick the entry that matches your test
|
|
230
|
+
runner — the public API (`createSpyFromClass`, `calledWith`, `resolveWith`, `nextWith`, …) is
|
|
231
|
+
identical across all three.
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { createSpyFromClass } from 'vitest-auto-spy'; // Vitest (default, zero-config)
|
|
235
|
+
import { createSpyFromClass } from 'vitest-auto-spy/bun'; // Bun — bun:test (next release)
|
|
236
|
+
import { createSpyFromClass } from 'vitest-auto-spy/node'; // node:test (next release)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
> Only the auto-spy helpers are normalised across runtimes; **native** mock methods stay the
|
|
240
|
+
> runner's own — `mockReturnValue` on Vitest/Bun, `spy.method.mock.mockImplementation` on
|
|
241
|
+
> `node:test`. Each entry registers its adapter on import, so import the one matching your runner.
|
|
242
|
+
|
|
243
|
+
> Using an observable spy (`observablePropsToSpyOn`, `nextWith`, …) without importing
|
|
244
|
+
> `vitest-auto-spy/rxjs` throws a clear hint telling you to add that import.
|
|
245
|
+
>
|
|
246
|
+
> The decoupling is at the **runtime** level. The core's _type_ surface (`Spy<T>`) still
|
|
247
|
+
> references rxjs types, so keep `rxjs` available for type-checking (it's normally already a
|
|
248
|
+
> devDependency); none of it reaches your runtime bundle.
|
|
249
|
+
>
|
|
250
|
+
> The same inversion-of-control applies to the **test runner**: the core no longer imports
|
|
251
|
+
> `vitest` directly — `vi.fn()` / `vi.spyOn()` sit behind a `MockAdapter` that the
|
|
252
|
+
> `vitest-auto-spy` entry registers by default, so it stays zero-config. This is the groundwork
|
|
253
|
+
> for running the exact same core on other Vitest-compatible runners.
|
|
254
|
+
|
|
255
|
+
## Comparison
|
|
256
|
+
|
|
257
|
+
| Library | Reads a class? | Return-type-aware helpers? | Runtimes | We win on |
|
|
258
|
+
| --- | :---: | :---: | --- | --- |
|
|
259
|
+
| **vitest-auto-spy** | ✅ | ✅ | Vitest (Bun · node:test next) | — |
|
|
260
|
+
| [jest-auto-spies](https://www.npmjs.com/package/jest-auto-spies) | ✅ | ✅ | Jest only | Vitest/Bun/Node successor, **same API** — direct migration path |
|
|
261
|
+
| [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)) |
|
|
262
|
+
| [@golevelup/ts-vitest](https://www.npmjs.com/package/@golevelup/ts-vitest) | partial | ❌ | Vitest | Typed `Promise`/`Observable` helpers + explicit class→spy + `mustBeCalledWith` |
|
|
263
|
+
| [sinon](https://www.npmjs.com/package/sinon) | ❌ (manual) | ❌ | Any | Auto-generated + fully typed vs. manual + loosely typed |
|
|
264
|
+
|
|
265
|
+
**The pitch:** the only auto-spy library that reads a **class** and gives a **fully-typed** spy of
|
|
266
|
+
every method with **return-type-aware** control helpers (`resolveWith` / `nextWith` / `calledWith`) —
|
|
267
|
+
across any Vitest-compatible runtime and framework.
|
|
268
|
+
|
|
108
269
|
## Migrating from jest-auto-spies
|
|
109
270
|
|
|
110
271
|
The public API is intentionally identical. In most projects the migration is a
|
|
@@ -118,7 +279,7 @@ The public API is intentionally identical. In most projects the migration is a
|
|
|
118
279
|
```
|
|
119
280
|
|
|
120
281
|
The only API-shape change from `jest-auto-spies` is that the Angular helpers and the
|
|
121
|
-
observable layer live behind the `/angular` and `/rxjs` subpaths (see [Entry points](#entry-points)).
|
|
282
|
+
observable layer live behind the `/angular` and `/rxjs` subpaths (see [Entry points & runtimes](#entry-points--runtimes)).
|
|
122
283
|
|
|
123
284
|
| jest-auto-spies | vitest-auto-spy | Status |
|
|
124
285
|
| --- | --- | --- |
|
|
@@ -152,6 +313,50 @@ createSpyFromClass(MyService, {
|
|
|
152
313
|
});
|
|
153
314
|
```
|
|
154
315
|
|
|
316
|
+
## Auto-mock by type (no class needed)
|
|
317
|
+
|
|
318
|
+
`createSpyFromClass` reads a real class's prototype. When you only have a TypeScript **interface or
|
|
319
|
+
type** (no runtime class), use `createAutoMock<T>()` — it builds the spy lazily from the type alone,
|
|
320
|
+
via a `Proxy`:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { createAutoMock } from 'vitest-auto-spy';
|
|
324
|
+
|
|
325
|
+
interface UserService {
|
|
326
|
+
getName(id: number): string;
|
|
327
|
+
getUser(id: number): Promise<User>;
|
|
328
|
+
apiUrl: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Before — needs a concrete class:
|
|
332
|
+
// const svc = createSpyFromClass(UserServiceClass);
|
|
333
|
+
|
|
334
|
+
// After — type only, no class:
|
|
335
|
+
const svc = createAutoMock<UserService>();
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Every accessed method becomes a decorated spy with the **same typed control helpers** as
|
|
339
|
+
`createSpyFromClass`, materialized lazily and cached (same reference on re-access):
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
svc.getName.calledWith(1).mockReturnValue('Ada'); // sync, arg-matched
|
|
343
|
+
svc.getUser.resolveWith({ id: 1, name: 'Ada' }); // promise helper
|
|
344
|
+
expect(svc.getName(1)).toBe('Ada');
|
|
345
|
+
await expect(svc.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Seed concrete values or implementations with the optional `overrides` argument (seeded keys are
|
|
349
|
+
returned as-is, never turned into spies):
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
const svc = createAutoMock<UserService>({ apiUrl: 'https://api.test' });
|
|
353
|
+
expect(svc.apiUrl).toBe('https://api.test'); // or assign: svc.apiUrl = '...'
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
> Caveat: with only a type at runtime, methods and plain properties are indistinguishable on
|
|
357
|
+
> access — an un-seeded property read returns a spy. Seed real property values via `overrides`
|
|
358
|
+
> (or assignment) to get them back verbatim.
|
|
359
|
+
|
|
155
360
|
## Synchronous methods
|
|
156
361
|
|
|
157
362
|
```ts
|
|
@@ -246,7 +451,144 @@ spy.userName = 'New Name';
|
|
|
246
451
|
expect(spy.accessorSpies.setters.userName).toHaveBeenCalledWith('New Name');
|
|
247
452
|
```
|
|
248
453
|
|
|
249
|
-
##
|
|
454
|
+
## Framework adapters
|
|
455
|
+
|
|
456
|
+
The core is framework-agnostic — `createSpyFromClass` / `createAutoMock` work in any test. The
|
|
457
|
+
subpaths below add a natural import and, where the framework has class DI, a tiny `provide*` helper.
|
|
458
|
+
None of them pull the framework into this package; they're recipes over the same core.
|
|
459
|
+
|
|
460
|
+
> **Angular** (`vitest-auto-spy/angular`) is published today. The **NestJS, React, Vue/Pinia and
|
|
461
|
+
> Svelte** entry points ship in the next release ([Availability](#availability)) — until then you
|
|
462
|
+
> can copy the recipe using the core `vitest-auto-spy` import directly.
|
|
463
|
+
|
|
464
|
+
### NestJS
|
|
465
|
+
|
|
466
|
+
Use `provideAutoSpy` to register a fully-mocked service in a `TestingModule`, then `injectSpy` to
|
|
467
|
+
pull it back out already typed as `Spy<T>`. `@nestjs/common` / `@nestjs/testing` are your own
|
|
468
|
+
(optional) peers — the helper imports neither:
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
import { Test, type TestingModule } from '@nestjs/testing';
|
|
472
|
+
import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/nestjs';
|
|
473
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
474
|
+
|
|
475
|
+
import { AuthService } from './auth.service';
|
|
476
|
+
import { UserService } from './user.service';
|
|
477
|
+
|
|
478
|
+
let moduleRef: TestingModule;
|
|
479
|
+
let userServiceSpy: Spy<UserService>;
|
|
480
|
+
|
|
481
|
+
beforeEach(async () => {
|
|
482
|
+
moduleRef = await Test.createTestingModule({
|
|
483
|
+
providers: [AuthService, provideAutoSpy(UserService)],
|
|
484
|
+
}).compile();
|
|
485
|
+
|
|
486
|
+
userServiceSpy = injectSpy(moduleRef, UserService);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('logs in a known user', () => {
|
|
490
|
+
userServiceSpy.findByEmail.mockReturnValue({ id: 1, name: 'Ada' });
|
|
491
|
+
|
|
492
|
+
const auth = moduleRef.get(AuthService);
|
|
493
|
+
expect(auth.login('ada@example.com')).toBeTruthy();
|
|
494
|
+
expect(userServiceSpy.findByEmail).toHaveBeenCalledWith('ada@example.com');
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### React (Testing Library)
|
|
499
|
+
|
|
500
|
+
React has no DI container, so there's no `provide*` helper — the recipe is: **spy the classes you
|
|
501
|
+
own** (services, stores, API clients, hook deps), then pass the spy into a Context provider or hook.
|
|
502
|
+
The spy is a plain object of spied functions, so it drops straight into `value={...}`:
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
import { render, screen } from '@testing-library/react';
|
|
506
|
+
import { createSpyFromClass, type Spy } from 'vitest-auto-spy/react';
|
|
507
|
+
import { CartContext, Cart } from './cart';
|
|
508
|
+
|
|
509
|
+
class CartStore {
|
|
510
|
+
getItemCount(): number { return 0; }
|
|
511
|
+
checkout(token: string): Promise<{ orderId: string }> { /* ... */ }
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
let cart: Spy<CartStore>;
|
|
515
|
+
|
|
516
|
+
beforeEach(() => {
|
|
517
|
+
cart = createSpyFromClass(CartStore); // every method is now a spy
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('shows the item count from the injected store', () => {
|
|
521
|
+
cart.getItemCount.mockReturnValue(3);
|
|
522
|
+
|
|
523
|
+
render(
|
|
524
|
+
<CartContext.Provider value={cart}>
|
|
525
|
+
<Cart />
|
|
526
|
+
</CartContext.Provider>,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
expect(screen.getByText('3 items')).toBeInTheDocument();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('drives async deps and asserts the component called them', async () => {
|
|
533
|
+
cart.checkout.resolveWith({ orderId: 'ord_42' });
|
|
534
|
+
// ...trigger checkout in the UI...
|
|
535
|
+
expect(cart.checkout).toHaveBeenCalledWith('tok_abc');
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Vue / Pinia
|
|
540
|
+
|
|
541
|
+
`provideAutoSpy(token, Class)` returns a `{ [token]: Spy<T> }` map you can spread into
|
|
542
|
+
`@vue/test-utils`' `global.provide`; for a class-based Pinia store, spy it directly:
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
import { mount } from '@vue/test-utils';
|
|
546
|
+
import { createSpyFromClass, provideAutoSpy } from 'vitest-auto-spy/vue';
|
|
547
|
+
|
|
548
|
+
// (a) class-based service injected via provide / global.provide
|
|
549
|
+
import { UserServiceKey, UserService } from '@/services/user.service';
|
|
550
|
+
|
|
551
|
+
const provide = provideAutoSpy(UserServiceKey, UserService); // { [UserServiceKey]: Spy<UserService> }
|
|
552
|
+
provide[UserServiceKey].getName.mockReturnValue('Fake Name');
|
|
553
|
+
|
|
554
|
+
const wrapper = mount(UserBadge, { global: { provide } });
|
|
555
|
+
expect(provide[UserServiceKey].getName).toHaveBeenCalled();
|
|
556
|
+
|
|
557
|
+
// (b) class-based Pinia store — every action becomes a spy
|
|
558
|
+
import { CartStore } from '@/stores/cart.store';
|
|
559
|
+
|
|
560
|
+
const store = createSpyFromClass(CartStore);
|
|
561
|
+
store.itemCount.mockReturnValue(3); // sync action/getter
|
|
562
|
+
store.checkout.resolveWith({ orderId: 'ord_42' }); // async action (Promise)
|
|
563
|
+
await store.checkout('tok_abc');
|
|
564
|
+
expect(store.checkout).toHaveBeenCalledWith('tok_abc');
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Svelte
|
|
568
|
+
|
|
569
|
+
Svelte has no class-based DI, so it's a recipe: keep your logic in plain class-based
|
|
570
|
+
services/stores, spy the class, and hand the spy to the component the same way it receives the real
|
|
571
|
+
one (props, context, or a mocked module):
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
import { render } from '@testing-library/svelte';
|
|
575
|
+
import { createSpyFromClass } from 'vitest-auto-spy/svelte';
|
|
576
|
+
import Cart from './Cart.svelte';
|
|
577
|
+
import { CartStore } from './cart-store';
|
|
578
|
+
|
|
579
|
+
it('shows the cart total from the store', () => {
|
|
580
|
+
const cartStore = createSpyFromClass(CartStore); // every method is a spy
|
|
581
|
+
|
|
582
|
+
cartStore.total.mockReturnValue(42);
|
|
583
|
+
cartStore.priceOf.calledWith('apple').mockReturnValue(7);
|
|
584
|
+
|
|
585
|
+
render(Cart, { props: { store: cartStore } });
|
|
586
|
+
|
|
587
|
+
expect(cartStore.total).toHaveBeenCalled();
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Angular
|
|
250
592
|
|
|
251
593
|
`provideAutoSpy` is the shorthand for providing an auto-spy in a `TestBed`:
|
|
252
594
|
|
|
@@ -274,7 +616,7 @@ beforeEach(() => {
|
|
|
274
616
|
> [`@analogjs/vite-plugin-angular`](https://www.npmjs.com/package/@analogjs/vite-plugin-angular)
|
|
275
617
|
> plus a TestBed setup file (e.g. `@analogjs/vitest-angular`'s `setupTestBed()`).
|
|
276
618
|
|
|
277
|
-
|
|
619
|
+
#### Signal / readonly property mocking (bonus)
|
|
278
620
|
|
|
279
621
|
```ts
|
|
280
622
|
import { mockReadonlyProp, mockReadonlyPropGetter, mockAccessorsProp } from 'vitest-auto-spy/angular';
|
|
@@ -289,8 +631,10 @@ mockAccessorsProp(service, 'theme'); // spied get + set
|
|
|
289
631
|
| Export | Description |
|
|
290
632
|
| --- | --- |
|
|
291
633
|
| `createSpyFromClass(Class, methodsOrConfig?)` | Build a fully-typed `Spy<T>` from a class |
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
634
|
+
| `createAutoMock<T>(overrides?)` | Build a `Spy<T>` from a **type/interface** alone (Proxy, no class) |
|
|
635
|
+
| `provideAutoSpy(Class, methodsOrConfig?)` | Angular / NestJS `{ provide, useValue }` shorthand |
|
|
636
|
+
| `provideAutoSpy(token, Class, methodsOrConfig?)` | Vue `{ [token]: Spy<T> }` for `global.provide` |
|
|
637
|
+
| `injectSpy(token)` _(Angular)_ / `injectSpy(moduleRef, token)` _(NestJS)_ | Inject typed as `Spy<T>` |
|
|
294
638
|
| `createFunctionSpy(name)` | A single standalone function spy with all helpers |
|
|
295
639
|
| `createObservableWithValues(configs, opts?)` | Build an Observable from value configs |
|
|
296
640
|
| `mockReadonlyProp` / `mockReadonlyPropGetter` / `mockAccessorsProp` | Mock readonly / accessor / signal props |
|
|
@@ -308,6 +652,38 @@ mockAccessorsProp(service, 'theme'); // spied get + set
|
|
|
308
652
|
|
|
309
653
|
`ValueConfig` (for `nextWithValues`): `{ value, delay? }` | `{ errorValue, delay? }` | `{ complete?, delay? }`.
|
|
310
654
|
|
|
655
|
+
## FAQ & troubleshooting
|
|
656
|
+
|
|
657
|
+
**"I get `X.nextWith is not a function` / observable helpers are missing."**
|
|
658
|
+
Import the rxjs layer once (e.g. in your test setup): `import 'vitest-auto-spy/rxjs';`. Without it,
|
|
659
|
+
requesting an observable spy throws a hint pointing you here.
|
|
660
|
+
|
|
661
|
+
**"My method isn't on the spy."**
|
|
662
|
+
Auto-discovery only sees **prototype methods**. Arrow-function class fields (`foo = () => {}`) and
|
|
663
|
+
plain properties aren't included — see [How it works](#how-it-works-and-what-it-wont-spy). List
|
|
664
|
+
getters/setters via `gettersToSpyOn` / `settersToSpyOn`.
|
|
665
|
+
|
|
666
|
+
**"Does it construct my class? Will the constructor's side effects run?"**
|
|
667
|
+
No. `createSpyFromClass` reads the prototype and never `new`s the class, so constructors (and their
|
|
668
|
+
HTTP/DB/`inject()` side effects) never run.
|
|
669
|
+
|
|
670
|
+
**"I only have an interface/type, not a class."**
|
|
671
|
+
Use [`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed) — it builds the spy lazily from the
|
|
672
|
+
type via a `Proxy`, no runtime class needed.
|
|
673
|
+
|
|
674
|
+
**"Can I use it without TypeScript?"**
|
|
675
|
+
Yes — the runtime works in plain JS; you just lose the compile-time `Spy<T>` typing.
|
|
676
|
+
|
|
677
|
+
**"Native mock methods differ between runners."**
|
|
678
|
+
Only the auto-spy helpers are normalised. Native APIs stay the runner's own (`mockReturnValue` on
|
|
679
|
+
Vitest/Bun, `spy.method.mock.mockImplementation` on `node:test`).
|
|
680
|
+
|
|
681
|
+
## Versioning
|
|
682
|
+
|
|
683
|
+
This package follows [Semantic Versioning](https://semver.org). Breaking changes to the public API
|
|
684
|
+
land only in major releases; see the [Changelog](./CHANGELOG.md) for what changed in each version.
|
|
685
|
+
Releases are automated from Conventional Commits (see [Contributing](#contributing)).
|
|
686
|
+
|
|
311
687
|
## Contributing
|
|
312
688
|
|
|
313
689
|
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) and the
|
|
@@ -322,11 +698,18 @@ npm run build
|
|
|
322
698
|
|
|
323
699
|
Releases are automated: merging a PR into `master` bumps the version from the
|
|
324
700
|
Conventional Commit types and publishes to npm — see
|
|
325
|
-
[CONTRIBUTING.md →
|
|
701
|
+
[CONTRIBUTING.md → Releasing](./CONTRIBUTING.md#releasing).
|
|
326
702
|
|
|
327
703
|
If this package saved you time, a ⭐ on [GitHub](https://github.com/ASDAlexey/vitest-auto-spy)
|
|
328
704
|
helps others find it.
|
|
329
705
|
|
|
706
|
+
## Acknowledgements
|
|
707
|
+
|
|
708
|
+
API and ergonomics are modelled on Shai Reznik's
|
|
709
|
+
[`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — `vitest-auto-spy` is its
|
|
710
|
+
Vitest-era successor with the same surface, so migrations are (mostly) a find-and-replace. Thanks to
|
|
711
|
+
the Vitest, Bun, RxJS and Angular communities whose tooling this builds on.
|
|
712
|
+
|
|
330
713
|
## License
|
|
331
714
|
|
|
332
715
|
[MIT](./LICENSE) © [Alexey Popov](https://github.com/ASDAlexey)
|
package/dist/angular.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var testing=require('@angular/core/testing')
|
|
1
|
+
'use strict';var vitest=require('vitest'),testing=require('@angular/core/testing');var y;function b(e){y=e;}var W="No mock adapter registered. Import a runtime entry once before creating spies \u2014 'vitest-auto-spy' (default, Vitest) or a runtime variant such as 'vitest-auto-spy/bun'.";function p(){if(!y)throw new Error(W);return y}var k={createMockFn(e,t){let r=e?vitest.vi.fn(e):vitest.vi.fn();return t!==void 0&&r.mockName(t),r},spyOnGetter(e,t){return vitest.vi.spyOn(e,t,"get")},spyOnSetter(e,t){return vitest.vi.spyOn(e,t,"set")},getCalls(e){return e.mock.calls},reset(e){e.mockReset();}};function T(e,t){Object.defineProperty(e,t,{get(){},set(r){},configurable:true});}function S(e,t,r){let n=p();return r==="setter"?n.spyOnSetter(e,t):n.spyOnGetter(e,t)}function O(e,t,r){let n={getters:{},setters:{}};e.accessorSpies=n,t.forEach(o=>{T(e,o),n.getters[o]=S(e,o,"getter");}),r.forEach(o=>{Object.prototype.hasOwnProperty.call(e,o)||T(e,o),n.setters[o]=S(e,o,"setter");});}function E(e){return `'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function _(e,t){return Object.entries(e).map(([r,n])=>`${r}:${i(n,t)}`).join(",")}function $(e,t){return `new Map([${[...e.entries()].map(([n,o])=>`[${i(n,t)},${i(o,t)}]`).join(",")}])`}function H(e,t){if(t.has(e))return "[Circular]";t.add(e);let r;return e instanceof Date?r=`new Date(${e.getTime()})`:e instanceof Map?r=$(e,t):e instanceof Set?r=`new Set([${[...e].map(n=>i(n,t)).join(",")}])`:Array.isArray(e)?r=`[${e.map(n=>i(n,t)).join(",")}]`:r=`{${_(e,t)}}`,t.delete(e),r}function i(e,t=new WeakSet){return typeof e=="string"?E(e):typeof e=="bigint"?`${e}n`:typeof e=="symbol"?e.toString():typeof e=="function"?`[Function: ${e.name}]`:typeof e=="object"&&e!==null?H(e,t):Object.is(e,-0)?"-0":String(e)}var f=class{#e={};set(t,r){this.#e[this.#t(t)]=r;}get(t){return this.#e[this.#t(t)]}#t(t){return i(t)}};var K=e=>`The function '${e}' was configured with 'mustBeCalledWith' and expects to be called with specific arguments. `,z="But the function was called without any arguments.",B=e=>{let t=i(e);return `But the actual arguments were: ${t.substring(1,t.length-1)}`},j={throwArgumentsError(e,t){let r=e.length===0?z:B(e);throw new Error(K(t)+r)}};var G="Observable spies require rxjs. Import 'vitest-auto-spy/rxjs' once (e.g. in your test setup) to enable observablePropsToSpyOn / nextWith / nextWithValues / throwWith / complete / returnSubject.";function C(){throw new Error(G);}function a(e,t){return Object.assign(e,t)}function I(e){return e.map(t=>({wrappedValue:t.delay===void 0?Promise.resolve(t.value):new Promise(r=>setTimeout(()=>r(t.value),t.delay))}))}function w(e,t){a(e,{resolveWith:r=>{t({value:Promise.resolve(r)});},rejectWith:r=>{t({value:r,_isRejectedPromise:true});},resolveWithPerCall:r=>{r.length!==0&&t({value:void 0,valuesPerCalls:I(r)});}});}function h(e,t){w(e,r=>{t.value=r.value,t._isRejectedPromise=r._isRejectedPromise??false,t.valuesPerCalls=r.valuesPerCalls??[];});}function M(e,t){w(e,r=>{e.argsToValuesMap.set(t,r);});}function D(e){return typeof e=="object"&&e!==null&&"value"in e}function F(e){if(e._isRejectedPromise)return Promise.reject(e.value);let t=e.valuesPerCalls?.shift();return t?t.wrappedValue:e.value}function v(e,t){let r=e.argsToValuesMap.get(t);return D(r)?{found:true,value:F(r)}:{found:false,value:void 0}}function q(e,t,r,n,o){if(e.wasConfigured){let s=v(e,n);if(s.found)return s.value}if(t.wasConfigured){let s=v(t,n);if(s.found)return s.value;j.throwArgumentsError(n,o);}return F(r)}function P(e,t){return e.wasConfigured=true,a(e,{mockReturnValue:r=>{e.argsToValuesMap.set(t,{value:r});}}),M(e,t),e}function A(){return {wasConfigured:false,argsToValuesMap:new f}}function R(e){let t=A(),r=A(),n={value:void 0},o=p().createMockFn((...c)=>q(t,r,n,c,e),e);h(o,n);let s=a(o,{calledWith:(...c)=>P(t,c),mustBeCalledWith:(...c)=>P(r,c)});return s}var x={methodsToSpyOn:[],observablePropsToSpyOn:[],settersToSpyOn:[],gettersToSpyOn:[]};function U(e){let t=Object.getOwnPropertyDescriptors(e);return Object.keys(t).filter(r=>r!=="constructor"&&!t[r]?.get)}function L(e){let t=new Set,r=e;for(;r;){let n=Object.getPrototypeOf(r);n&&U(r).forEach(o=>t.add(o)),r=n;}return [...t]}function N(e){return e?Array.isArray(e)?{...x,methodsToSpyOn:e}:{methodsToSpyOn:e.methodsToSpyOn??[],observablePropsToSpyOn:e.observablePropsToSpyOn??[],settersToSpyOn:e.settersToSpyOn??[],gettersToSpyOn:e.gettersToSpyOn??[]}:{...x}}function V(e,t){let{methodsToSpyOn:r,observablePropsToSpyOn:n,settersToSpyOn:o,gettersToSpyOn:s}=N(t),c=r.length>0?r:L(e.prototype),u={};return n.forEach(l=>{u[l]=C().createPropSpy();}),O(u,s,o),c.forEach(l=>{u[l]=R(l);}),u}function X(e,t){return {provide:e,useValue:V(e,t)}}function Y(e){return testing.TestBed.inject(e)}function Q(e,t,r){Object.defineProperty(e,t,{get:()=>r,configurable:true});}function Z(e,t,r){Object.defineProperty(e,t,{get:r,configurable:true});}function ee(e,t){let r=p();Object.defineProperty(e,t,{get:r.createMockFn(),set:r.createMockFn(),configurable:true});}b(k);exports.injectSpy=Y;exports.mockAccessorsProp=ee;exports.mockReadonlyProp=Q;exports.mockReadonlyPropGetter=Z;exports.provideAutoSpy=X;
|
package/dist/angular.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-
|
|
1
|
+
import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-C84-zP_V.cjs';
|
|
2
2
|
import 'rxjs';
|
|
3
3
|
import 'vitest';
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ declare function injectSpy<T>(token: ClassType<T> | (abstract new (...args: neve
|
|
|
15
15
|
declare function mockReadonlyProp<T, K extends keyof T>(object: T, property: K, value: T[K]): void;
|
|
16
16
|
/** Override a readonly property with a dynamic getter. */
|
|
17
17
|
declare function mockReadonlyPropGetter<T, K extends keyof T>(object: T, property: K, getter: () => unknown): void;
|
|
18
|
-
/** Replace a property with spied `get`/`set` accessors (
|
|
18
|
+
/** Replace a property with spied `get`/`set` accessors (host-runner mocks). */
|
|
19
19
|
declare function mockAccessorsProp<T, K extends keyof T>(object: T, property: K): void;
|
|
20
20
|
|
|
21
21
|
export { type AngularValueProvider, injectSpy, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, provideAutoSpy };
|
package/dist/angular.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-
|
|
1
|
+
import { C as ClassType, S as Spy, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-C84-zP_V.js';
|
|
2
2
|
import 'rxjs';
|
|
3
3
|
import 'vitest';
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ declare function injectSpy<T>(token: ClassType<T> | (abstract new (...args: neve
|
|
|
15
15
|
declare function mockReadonlyProp<T, K extends keyof T>(object: T, property: K, value: T[K]): void;
|
|
16
16
|
/** Override a readonly property with a dynamic getter. */
|
|
17
17
|
declare function mockReadonlyPropGetter<T, K extends keyof T>(object: T, property: K, getter: () => unknown): void;
|
|
18
|
-
/** Replace a property with spied `get`/`set` accessors (
|
|
18
|
+
/** Replace a property with spied `get`/`set` accessors (host-runner mocks). */
|
|
19
19
|
declare function mockAccessorsProp<T, K extends keyof T>(object: T, property: K): void;
|
|
20
20
|
|
|
21
21
|
export { type AngularValueProvider, injectSpy, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, provideAutoSpy };
|
package/dist/angular.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {a as a$1}from'./chunk-6LFQMHSX.js';import {a,e,b}from'./chunk-VLNCYBUN.js';import'./chunk-7IUYJ6ZW.js';import {TestBed}from'@angular/core/testing';function c(e$1,o){return {provide:e$1,useValue:e(e$1,o)}}function s(e){return TestBed.inject(e)}function y(e,o,r){Object.defineProperty(e,o,{get:()=>r,configurable:true});}function d(e,o,r){Object.defineProperty(e,o,{get:r,configurable:true});}function u(e,o){let r=b();Object.defineProperty(e,o,{get:r.createMockFn(),set:r.createMockFn(),configurable:true});}a(a$1);export{s as injectSpy,u as mockAccessorsProp,y as mockReadonlyProp,d as mockReadonlyPropGetter,c as provideAutoSpy};
|
package/dist/bun.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var bun_test=require('bun:test');function S({createMockFn:e,getCalls:t,reset:r}){return {createMockFn:e,spyOnGetter:(o,n)=>b(e,o,n,"get"),spyOnSetter:(o,n)=>b(e,o,n,"set"),getCalls:t,reset:r}}function b(e,t,r,o){let n=Object.getOwnPropertyDescriptor(t,r),s=o==="get"?n?.get:n?.set,i=e(s),c={configurable:true,enumerable:n?.enumerable??true};return n?.get&&(c.get=n.get),n?.set&&(c.set=n.set),o==="get"?c.get=i:c.set=i,Object.defineProperty(t,r,c),i}function w(e){return S({createMockFn:(r,o)=>{let n=e.mock(r??(()=>{}));return o!==void 0&&n.mockName&&n.mockName(o),n},getCalls:r=>r.mock.calls,reset:r=>r.mockReset()})}var y;function M(e){y=e;}var W="No mock adapter registered. Import a runtime entry once before creating spies \u2014 'vitest-auto-spy' (default, Vitest) or a runtime variant such as 'vitest-auto-spy/bun'.";function d(){if(!y)throw new Error(W);return y}function O(e,t){Object.defineProperty(e,t,{get(){},set(r){},configurable:true});}function T(e,t,r){let o=d();return r==="setter"?o.spyOnSetter(e,t):o.spyOnGetter(e,t)}function h(e,t,r){let o={getters:{},setters:{}};e.accessorSpies=o,t.forEach(n=>{O(e,n),o.getters[n]=T(e,n,"getter");}),r.forEach(n=>{Object.prototype.hasOwnProperty.call(e,n)||O(e,n),o.setters[n]=T(e,n,"setter");});}function E(e){return `'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function _(e,t){return Object.entries(e).map(([r,o])=>`${r}:${a(o,t)}`).join(",")}function B(e,t){return `new Map([${[...e.entries()].map(([o,n])=>`[${a(o,t)},${a(n,t)}]`).join(",")}])`}function H(e,t){if(t.has(e))return "[Circular]";t.add(e);let r;return e instanceof Date?r=`new Date(${e.getTime()})`:e instanceof Map?r=B(e,t):e instanceof Set?r=`new Set([${[...e].map(o=>a(o,t)).join(",")}])`:Array.isArray(e)?r=`[${e.map(o=>a(o,t)).join(",")}]`:r=`{${_(e,t)}}`,t.delete(e),r}function a(e,t=new WeakSet){return typeof e=="string"?E(e):typeof e=="bigint"?`${e}n`:typeof e=="symbol"?e.toString():typeof e=="function"?`[Function: ${e.name}]`:typeof e=="object"&&e!==null?H(e,t):Object.is(e,-0)?"-0":String(e)}var f=class{#e={};set(t,r){this.#e[this.#t(t)]=r;}get(t){return this.#e[this.#t(t)]}#t(t){return a(t)}};var $=e=>`The function '${e}' was configured with 'mustBeCalledWith' and expects to be called with specific arguments. `,z="But the function was called without any arguments.",D=e=>{let t=a(e);return `But the actual arguments were: ${t.substring(1,t.length-1)}`},m={throwArgumentsError(e,t){let r=e.length===0?z:D(e);throw new Error($(t)+r)}};var I="Observable spies require rxjs. Import 'vitest-auto-spy/rxjs' once (e.g. in your test setup) to enable observablePropsToSpyOn / nextWith / nextWithValues / throwWith / complete / returnSubject.";function C(){throw new Error(I);}function u(e,t){return Object.assign(e,t)}function G(e){return e.map(t=>({wrappedValue:t.delay===void 0?Promise.resolve(t.value):new Promise(r=>setTimeout(()=>r(t.value),t.delay))}))}function j(e,t){u(e,{resolveWith:r=>{t({value:Promise.resolve(r)});},rejectWith:r=>{t({value:r,_isRejectedPromise:true});},resolveWithPerCall:r=>{r.length!==0&&t({value:void 0,valuesPerCalls:G(r)});}});}function F(e,t){j(e,r=>{t.value=r.value,t._isRejectedPromise=r._isRejectedPromise??false,t.valuesPerCalls=r.valuesPerCalls??[];});}function A(e,t){j(e,r=>{e.argsToValuesMap.set(t,r);});}function K(e){return typeof e=="object"&&e!==null&&"value"in e}function x(e){if(e._isRejectedPromise)return Promise.reject(e.value);let t=e.valuesPerCalls?.shift();return t?t.wrappedValue:e.value}function P(e,t){let r=e.argsToValuesMap.get(t);return K(r)?{found:true,value:x(r)}:{found:false,value:void 0}}function N(e,t,r,o,n){if(e.wasConfigured){let s=P(e,o);if(s.found)return s.value}if(t.wasConfigured){let s=P(t,o);if(s.found)return s.value;m.throwArgumentsError(o,n);}return x(r)}function v(e,t){return e.wasConfigured=true,u(e,{mockReturnValue:r=>{e.argsToValuesMap.set(t,{value:r});}}),A(e,t),e}function R(){return {wasConfigured:false,argsToValuesMap:new f}}function p(e){let t=R(),r=R(),o={value:void 0},n=d().createMockFn((...i)=>N(t,r,o,i,e),e);F(n,o);let s=u(n,{calledWith:(...i)=>v(t,i),mustBeCalledWith:(...i)=>v(r,i)});return s}var V={methodsToSpyOn:[],observablePropsToSpyOn:[],settersToSpyOn:[],gettersToSpyOn:[]};function q(e){let t=Object.getOwnPropertyDescriptors(e);return Object.keys(t).filter(r=>r!=="constructor"&&!t[r]?.get)}function U(e){let t=new Set,r=e;for(;r;){let o=Object.getPrototypeOf(r);o&&q(r).forEach(n=>t.add(n)),r=o;}return [...t]}function L(e){return e?Array.isArray(e)?{...V,methodsToSpyOn:e}:{methodsToSpyOn:e.methodsToSpyOn??[],observablePropsToSpyOn:e.observablePropsToSpyOn??[],settersToSpyOn:e.settersToSpyOn??[],gettersToSpyOn:e.gettersToSpyOn??[]}:{...V}}function J(e,t){let{methodsToSpyOn:r,observablePropsToSpyOn:o,settersToSpyOn:n,gettersToSpyOn:s}=L(t),i=r.length>0?r:U(e.prototype),c={};return o.forEach(l=>{c[l]=C().createPropSpy();}),h(c,s,n),i.forEach(l=>{c[l]=p(l);}),c}function X(e={}){let t=new Map;for(let o of Reflect.ownKeys(e))t.set(o,Reflect.get(e,o));let r={get(o,n){if(t.has(n))return t.get(n);if(typeof n=="symbol"||n==="then"||n==="constructor")return;let s=p(String(n));return t.set(n,s),s},set(o,n,s){return t.set(n,s),true},has(o,n){return t.has(n)},ownKeys(){return [...t.keys()]},getOwnPropertyDescriptor(o,n){if(t.has(n))return {configurable:true,enumerable:true,value:t.get(n),writable:true}}};return new Proxy({},r)}M(w({mock:bun_test.mock}));exports.createAutoMock=X;exports.createFunctionSpy=p;exports.createSpyFromClass=J;exports.errorHandler=m;
|