vitest-auto-spy 1.3.0 → 1.5.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 +414 -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`). Runs on **Vitest**,
|
|
9
|
+
**Bun** (`bun:test`) and **`node:test`** behind one identical API, with **RxJS** spies and
|
|
10
|
+
**Angular / NestJS / React / Vue·Pinia / Svelte** recipes ([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**, **Bun** and **`node:test`**, identical API on each
|
|
35
|
+
- 🧩 Framework recipes: **Angular**, **NestJS**, **React**, **Vue/Pinia** and **Svelte**
|
|
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,76 @@ 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
|
+
> **All entry points are published.** As of `vitest-auto-spy@1.4.0`, the **Vitest / Bun / `node:test`**
|
|
101
|
+
> runtimes, the **RxJS** layer, and the **Angular / NestJS / React / Vue·Pinia / Svelte** recipes all
|
|
102
|
+
> ship as importable entry points — one identical API across every runner and framework.
|
|
103
|
+
|
|
104
|
+
| Entry point | Status |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `vitest-auto-spy` · `vitest-auto-spy/rxjs` · `vitest-auto-spy/angular` | ✅ **Published** (1.4.0) |
|
|
107
|
+
| `vitest-auto-spy/bun` · `vitest-auto-spy/node` | ✅ **Published** (1.4.0) |
|
|
108
|
+
| `vitest-auto-spy/nestjs` · `/react` · `/vue` · `/svelte` | ✅ **Published** (1.4.0) |
|
|
109
|
+
|
|
110
|
+
## Quick start
|
|
111
|
+
|
|
112
|
+
Pass a class — every method becomes a typed spy, and the **constructor is never called** (no side
|
|
113
|
+
effects). The helper you get on each method matches its return type:
|
|
67
114
|
|
|
68
115
|
```ts
|
|
69
|
-
import {
|
|
70
|
-
import 'vitest-auto-spy
|
|
71
|
-
|
|
116
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
117
|
+
import { createSpyFromClass, type Spy } from 'vitest-auto-spy';
|
|
118
|
+
|
|
119
|
+
class UserService {
|
|
120
|
+
getName(id: number): string {
|
|
121
|
+
return 'real name';
|
|
122
|
+
}
|
|
123
|
+
async getUser(id: number): Promise<{ id: number; name: string }> {
|
|
124
|
+
return fetchUser(id);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let userService: Spy<UserService>;
|
|
129
|
+
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
userService = createSpyFromClass(UserService); // every method is now a spy
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('stubs each method with the right helper for its return type', async () => {
|
|
135
|
+
userService.getName.mockReturnValue('Ada'); // sync
|
|
136
|
+
userService.getUser.resolveWith({ id: 1, name: 'Ada' }); // Promise helper
|
|
137
|
+
|
|
138
|
+
expect(userService.getName(1)).toBe('Ada');
|
|
139
|
+
await expect(userService.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
|
|
140
|
+
expect(userService.getName).toHaveBeenCalledWith(1);
|
|
141
|
+
});
|
|
72
142
|
```
|
|
73
143
|
|
|
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.
|
|
144
|
+
No class, only a TypeScript type? Reach for
|
|
145
|
+
[`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed).
|
|
80
146
|
|
|
81
147
|
## Why
|
|
82
148
|
|
|
@@ -105,6 +171,100 @@ beforeEach(() => {
|
|
|
105
171
|
`Spy<UserService>` exposes each method as a `vi.fn()` **plus** the right helpers based on
|
|
106
172
|
the method's return type (sync / `Promise` / `Observable`).
|
|
107
173
|
|
|
174
|
+
## How it works (and what it won't spy)
|
|
175
|
+
|
|
176
|
+
`createSpyFromClass(MyService)` reads `MyService.prototype` and walks the **prototype chain** — it
|
|
177
|
+
never `new`s the class. Concretely:
|
|
178
|
+
|
|
179
|
+
- ✅ **The class is never instantiated.** The constructor and its side effects (HTTP clients, DB
|
|
180
|
+
connections, `inject()` calls) never run — you pass the class itself, not an instance.
|
|
181
|
+
- ✅ **Inherited methods are spied too**, all the way up the prototype chain.
|
|
182
|
+
- ✅ Each method is replaced by a fresh spy carrying the helpers that match its **return type**:
|
|
183
|
+
sync → `mockReturnValue` / `calledWith`; `Promise` → `resolveWith` / `rejectWith`; `Observable`
|
|
184
|
+
→ `nextWith` / `throwWith` / … .
|
|
185
|
+
|
|
186
|
+
What it **won't** auto-discover — by design, because these aren't prototype methods:
|
|
187
|
+
|
|
188
|
+
- ⚠️ **Arrow-function class fields** (`doThing = () => {}`) are instance properties set in the
|
|
189
|
+
constructor, so prototype scanning can't see them. Use regular methods, list them explicitly, or
|
|
190
|
+
mock them by hand. (Same constraint as `jest-auto-spies`.)
|
|
191
|
+
- ⚠️ **Getters / setters** are skipped unless named in `gettersToSpyOn` / `settersToSpyOn` — see
|
|
192
|
+
[Getters & setters](#getters--setters).
|
|
193
|
+
- ⚠️ **Plain data properties** carry no value until you set one; auto-spy mocks *behaviour*
|
|
194
|
+
(methods), not state. To mock by type including properties, use
|
|
195
|
+
[`createAutoMock`](#auto-mock-by-type-no-class-needed).
|
|
196
|
+
|
|
197
|
+
## Entry points & runtimes
|
|
198
|
+
|
|
199
|
+
The library ships a framework-agnostic core plus runtime and framework layers, so a plain
|
|
200
|
+
Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
|
|
201
|
+
|
|
202
|
+
| Import | Provides | Pulls in | Status |
|
|
203
|
+
| --- | --- | --- | :---: |
|
|
204
|
+
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
|
|
205
|
+
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) + `createObservableWithValues` | `rxjs` | ✅ |
|
|
206
|
+
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `mockReadonlyProp*`, `mockAccessorsProp` | `@angular/core` | ✅ |
|
|
207
|
+
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
208
|
+
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()` | `node:test` | ✅ |
|
|
209
|
+
| `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`) | ✅ |
|
|
210
|
+
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | ✅ |
|
|
211
|
+
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | ✅ |
|
|
212
|
+
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | ✅ |
|
|
213
|
+
|
|
214
|
+
✅ all entry points published in 1.4.0 (see [Availability](#availability)).
|
|
215
|
+
|
|
216
|
+
> The framework subpaths import **nothing** from their framework — the helpers are structural, so
|
|
217
|
+
> `@nestjs/*`, `react`, `vue`/`pinia` and `svelte` stay your own (already-present) dev dependencies and
|
|
218
|
+
> never reach this package's runtime bundle.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { createSpyFromClass } from 'vitest-auto-spy';
|
|
222
|
+
import 'vitest-auto-spy/rxjs'; // once (e.g. in your test setup) — enables observable spies
|
|
223
|
+
import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/angular';
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Runtimes
|
|
227
|
+
|
|
228
|
+
The core is runner-agnostic behind a `MockAdapter`: pick the entry that matches your test
|
|
229
|
+
runner — the public API (`createSpyFromClass`, `calledWith`, `resolveWith`, `nextWith`, …) is
|
|
230
|
+
identical across all three.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { createSpyFromClass } from 'vitest-auto-spy'; // Vitest (default, zero-config)
|
|
234
|
+
import { createSpyFromClass } from 'vitest-auto-spy/bun'; // Bun — bun:test
|
|
235
|
+
import { createSpyFromClass } from 'vitest-auto-spy/node'; // node:test
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
> Only the auto-spy helpers are normalised across runtimes; **native** mock methods stay the
|
|
239
|
+
> runner's own — `mockReturnValue` on Vitest/Bun, `spy.method.mock.mockImplementation` on
|
|
240
|
+
> `node:test`. Each entry registers its adapter on import, so import the one matching your runner.
|
|
241
|
+
|
|
242
|
+
> Using an observable spy (`observablePropsToSpyOn`, `nextWith`, …) without importing
|
|
243
|
+
> `vitest-auto-spy/rxjs` throws a clear hint telling you to add that import.
|
|
244
|
+
>
|
|
245
|
+
> The decoupling is at the **runtime** level. The core's _type_ surface (`Spy<T>`) still
|
|
246
|
+
> references rxjs types, so keep `rxjs` available for type-checking (it's normally already a
|
|
247
|
+
> devDependency); none of it reaches your runtime bundle.
|
|
248
|
+
>
|
|
249
|
+
> The same inversion-of-control applies to the **test runner**: the core no longer imports
|
|
250
|
+
> `vitest` directly — `vi.fn()` / `vi.spyOn()` sit behind a `MockAdapter` that the
|
|
251
|
+
> `vitest-auto-spy` entry registers by default, so it stays zero-config. This is the groundwork
|
|
252
|
+
> for running the exact same core on other Vitest-compatible runners.
|
|
253
|
+
|
|
254
|
+
## Comparison
|
|
255
|
+
|
|
256
|
+
| Library | Reads a class? | Return-type-aware helpers? | Runtimes | We win on |
|
|
257
|
+
| --- | :---: | :---: | --- | --- |
|
|
258
|
+
| **vitest-auto-spy** | ✅ | ✅ | Vitest (Bun · node:test next) | — |
|
|
259
|
+
| [jest-auto-spies](https://www.npmjs.com/package/jest-auto-spies) | ✅ | ✅ | Jest only | Vitest/Bun/Node successor, **same API** — direct migration path |
|
|
260
|
+
| [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)) |
|
|
261
|
+
| [@golevelup/ts-vitest](https://www.npmjs.com/package/@golevelup/ts-vitest) | partial | ❌ | Vitest | Typed `Promise`/`Observable` helpers + explicit class→spy + `mustBeCalledWith` |
|
|
262
|
+
| [sinon](https://www.npmjs.com/package/sinon) | ❌ (manual) | ❌ | Any | Auto-generated + fully typed vs. manual + loosely typed |
|
|
263
|
+
|
|
264
|
+
**The pitch:** the only auto-spy library that reads a **class** and gives a **fully-typed** spy of
|
|
265
|
+
every method with **return-type-aware** control helpers (`resolveWith` / `nextWith` / `calledWith`) —
|
|
266
|
+
across any Vitest-compatible runtime and framework.
|
|
267
|
+
|
|
108
268
|
## Migrating from jest-auto-spies
|
|
109
269
|
|
|
110
270
|
The public API is intentionally identical. In most projects the migration is a
|
|
@@ -118,7 +278,7 @@ The public API is intentionally identical. In most projects the migration is a
|
|
|
118
278
|
```
|
|
119
279
|
|
|
120
280
|
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)).
|
|
281
|
+
observable layer live behind the `/angular` and `/rxjs` subpaths (see [Entry points & runtimes](#entry-points--runtimes)).
|
|
122
282
|
|
|
123
283
|
| jest-auto-spies | vitest-auto-spy | Status |
|
|
124
284
|
| --- | --- | --- |
|
|
@@ -152,6 +312,50 @@ createSpyFromClass(MyService, {
|
|
|
152
312
|
});
|
|
153
313
|
```
|
|
154
314
|
|
|
315
|
+
## Auto-mock by type (no class needed)
|
|
316
|
+
|
|
317
|
+
`createSpyFromClass` reads a real class's prototype. When you only have a TypeScript **interface or
|
|
318
|
+
type** (no runtime class), use `createAutoMock<T>()` — it builds the spy lazily from the type alone,
|
|
319
|
+
via a `Proxy`:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { createAutoMock } from 'vitest-auto-spy';
|
|
323
|
+
|
|
324
|
+
interface UserService {
|
|
325
|
+
getName(id: number): string;
|
|
326
|
+
getUser(id: number): Promise<User>;
|
|
327
|
+
apiUrl: string;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Before — needs a concrete class:
|
|
331
|
+
// const svc = createSpyFromClass(UserServiceClass);
|
|
332
|
+
|
|
333
|
+
// After — type only, no class:
|
|
334
|
+
const svc = createAutoMock<UserService>();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Every accessed method becomes a decorated spy with the **same typed control helpers** as
|
|
338
|
+
`createSpyFromClass`, materialized lazily and cached (same reference on re-access):
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
svc.getName.calledWith(1).mockReturnValue('Ada'); // sync, arg-matched
|
|
342
|
+
svc.getUser.resolveWith({ id: 1, name: 'Ada' }); // promise helper
|
|
343
|
+
expect(svc.getName(1)).toBe('Ada');
|
|
344
|
+
await expect(svc.getUser(1)).resolves.toEqual({ id: 1, name: 'Ada' });
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Seed concrete values or implementations with the optional `overrides` argument (seeded keys are
|
|
348
|
+
returned as-is, never turned into spies):
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
const svc = createAutoMock<UserService>({ apiUrl: 'https://api.test' });
|
|
352
|
+
expect(svc.apiUrl).toBe('https://api.test'); // or assign: svc.apiUrl = '...'
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
> Caveat: with only a type at runtime, methods and plain properties are indistinguishable on
|
|
356
|
+
> access — an un-seeded property read returns a spy. Seed real property values via `overrides`
|
|
357
|
+
> (or assignment) to get them back verbatim.
|
|
358
|
+
|
|
155
359
|
## Synchronous methods
|
|
156
360
|
|
|
157
361
|
```ts
|
|
@@ -246,7 +450,144 @@ spy.userName = 'New Name';
|
|
|
246
450
|
expect(spy.accessorSpies.setters.userName).toHaveBeenCalledWith('New Name');
|
|
247
451
|
```
|
|
248
452
|
|
|
249
|
-
##
|
|
453
|
+
## Framework adapters
|
|
454
|
+
|
|
455
|
+
The core is framework-agnostic — `createSpyFromClass` / `createAutoMock` work in any test. The
|
|
456
|
+
subpaths below add a natural import and, where the framework has class DI, a tiny `provide*` helper.
|
|
457
|
+
None of them pull the framework into this package; they're recipes over the same core.
|
|
458
|
+
|
|
459
|
+
> The **Angular**, **NestJS**, **React**, **Vue/Pinia** and **Svelte** entry points are all published
|
|
460
|
+
> ([Availability](#availability)). Each is a thin recipe over the same core, so you can equally copy it
|
|
461
|
+
> using the core `vitest-auto-spy` import directly.
|
|
462
|
+
|
|
463
|
+
### NestJS
|
|
464
|
+
|
|
465
|
+
Use `provideAutoSpy` to register a fully-mocked service in a `TestingModule`, then `injectSpy` to
|
|
466
|
+
pull it back out already typed as `Spy<T>`. `@nestjs/common` / `@nestjs/testing` are your own
|
|
467
|
+
(optional) peers — the helper imports neither:
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
import { Test, type TestingModule } from '@nestjs/testing';
|
|
471
|
+
import { provideAutoSpy, injectSpy } from 'vitest-auto-spy/nestjs';
|
|
472
|
+
import { beforeEach, expect, it } from 'vitest';
|
|
473
|
+
|
|
474
|
+
import { AuthService } from './auth.service';
|
|
475
|
+
import { UserService } from './user.service';
|
|
476
|
+
|
|
477
|
+
let moduleRef: TestingModule;
|
|
478
|
+
let userServiceSpy: Spy<UserService>;
|
|
479
|
+
|
|
480
|
+
beforeEach(async () => {
|
|
481
|
+
moduleRef = await Test.createTestingModule({
|
|
482
|
+
providers: [AuthService, provideAutoSpy(UserService)],
|
|
483
|
+
}).compile();
|
|
484
|
+
|
|
485
|
+
userServiceSpy = injectSpy(moduleRef, UserService);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('logs in a known user', () => {
|
|
489
|
+
userServiceSpy.findByEmail.mockReturnValue({ id: 1, name: 'Ada' });
|
|
490
|
+
|
|
491
|
+
const auth = moduleRef.get(AuthService);
|
|
492
|
+
expect(auth.login('ada@example.com')).toBeTruthy();
|
|
493
|
+
expect(userServiceSpy.findByEmail).toHaveBeenCalledWith('ada@example.com');
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### React (Testing Library)
|
|
498
|
+
|
|
499
|
+
React has no DI container, so there's no `provide*` helper — the recipe is: **spy the classes you
|
|
500
|
+
own** (services, stores, API clients, hook deps), then pass the spy into a Context provider or hook.
|
|
501
|
+
The spy is a plain object of spied functions, so it drops straight into `value={...}`:
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
import { render, screen } from '@testing-library/react';
|
|
505
|
+
import { createSpyFromClass, type Spy } from 'vitest-auto-spy/react';
|
|
506
|
+
import { CartContext, Cart } from './cart';
|
|
507
|
+
|
|
508
|
+
class CartStore {
|
|
509
|
+
getItemCount(): number { return 0; }
|
|
510
|
+
checkout(token: string): Promise<{ orderId: string }> { /* ... */ }
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
let cart: Spy<CartStore>;
|
|
514
|
+
|
|
515
|
+
beforeEach(() => {
|
|
516
|
+
cart = createSpyFromClass(CartStore); // every method is now a spy
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('shows the item count from the injected store', () => {
|
|
520
|
+
cart.getItemCount.mockReturnValue(3);
|
|
521
|
+
|
|
522
|
+
render(
|
|
523
|
+
<CartContext.Provider value={cart}>
|
|
524
|
+
<Cart />
|
|
525
|
+
</CartContext.Provider>,
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
expect(screen.getByText('3 items')).toBeInTheDocument();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('drives async deps and asserts the component called them', async () => {
|
|
532
|
+
cart.checkout.resolveWith({ orderId: 'ord_42' });
|
|
533
|
+
// ...trigger checkout in the UI...
|
|
534
|
+
expect(cart.checkout).toHaveBeenCalledWith('tok_abc');
|
|
535
|
+
});
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Vue / Pinia
|
|
539
|
+
|
|
540
|
+
`provideAutoSpy(token, Class)` returns a `{ [token]: Spy<T> }` map you can spread into
|
|
541
|
+
`@vue/test-utils`' `global.provide`; for a class-based Pinia store, spy it directly:
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
import { mount } from '@vue/test-utils';
|
|
545
|
+
import { createSpyFromClass, provideAutoSpy } from 'vitest-auto-spy/vue';
|
|
546
|
+
|
|
547
|
+
// (a) class-based service injected via provide / global.provide
|
|
548
|
+
import { UserServiceKey, UserService } from '@/services/user.service';
|
|
549
|
+
|
|
550
|
+
const provide = provideAutoSpy(UserServiceKey, UserService); // { [UserServiceKey]: Spy<UserService> }
|
|
551
|
+
provide[UserServiceKey].getName.mockReturnValue('Fake Name');
|
|
552
|
+
|
|
553
|
+
const wrapper = mount(UserBadge, { global: { provide } });
|
|
554
|
+
expect(provide[UserServiceKey].getName).toHaveBeenCalled();
|
|
555
|
+
|
|
556
|
+
// (b) class-based Pinia store — every action becomes a spy
|
|
557
|
+
import { CartStore } from '@/stores/cart.store';
|
|
558
|
+
|
|
559
|
+
const store = createSpyFromClass(CartStore);
|
|
560
|
+
store.itemCount.mockReturnValue(3); // sync action/getter
|
|
561
|
+
store.checkout.resolveWith({ orderId: 'ord_42' }); // async action (Promise)
|
|
562
|
+
await store.checkout('tok_abc');
|
|
563
|
+
expect(store.checkout).toHaveBeenCalledWith('tok_abc');
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Svelte
|
|
567
|
+
|
|
568
|
+
Svelte has no class-based DI, so it's a recipe: keep your logic in plain class-based
|
|
569
|
+
services/stores, spy the class, and hand the spy to the component the same way it receives the real
|
|
570
|
+
one (props, context, or a mocked module):
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
import { render } from '@testing-library/svelte';
|
|
574
|
+
import { createSpyFromClass } from 'vitest-auto-spy/svelte';
|
|
575
|
+
import Cart from './Cart.svelte';
|
|
576
|
+
import { CartStore } from './cart-store';
|
|
577
|
+
|
|
578
|
+
it('shows the cart total from the store', () => {
|
|
579
|
+
const cartStore = createSpyFromClass(CartStore); // every method is a spy
|
|
580
|
+
|
|
581
|
+
cartStore.total.mockReturnValue(42);
|
|
582
|
+
cartStore.priceOf.calledWith('apple').mockReturnValue(7);
|
|
583
|
+
|
|
584
|
+
render(Cart, { props: { store: cartStore } });
|
|
585
|
+
|
|
586
|
+
expect(cartStore.total).toHaveBeenCalled();
|
|
587
|
+
});
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Angular
|
|
250
591
|
|
|
251
592
|
`provideAutoSpy` is the shorthand for providing an auto-spy in a `TestBed`:
|
|
252
593
|
|
|
@@ -274,7 +615,7 @@ beforeEach(() => {
|
|
|
274
615
|
> [`@analogjs/vite-plugin-angular`](https://www.npmjs.com/package/@analogjs/vite-plugin-angular)
|
|
275
616
|
> plus a TestBed setup file (e.g. `@analogjs/vitest-angular`'s `setupTestBed()`).
|
|
276
617
|
|
|
277
|
-
|
|
618
|
+
#### Signal / readonly property mocking (bonus)
|
|
278
619
|
|
|
279
620
|
```ts
|
|
280
621
|
import { mockReadonlyProp, mockReadonlyPropGetter, mockAccessorsProp } from 'vitest-auto-spy/angular';
|
|
@@ -289,8 +630,10 @@ mockAccessorsProp(service, 'theme'); // spied get + set
|
|
|
289
630
|
| Export | Description |
|
|
290
631
|
| --- | --- |
|
|
291
632
|
| `createSpyFromClass(Class, methodsOrConfig?)` | Build a fully-typed `Spy<T>` from a class |
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
633
|
+
| `createAutoMock<T>(overrides?)` | Build a `Spy<T>` from a **type/interface** alone (Proxy, no class) |
|
|
634
|
+
| `provideAutoSpy(Class, methodsOrConfig?)` | Angular / NestJS `{ provide, useValue }` shorthand |
|
|
635
|
+
| `provideAutoSpy(token, Class, methodsOrConfig?)` | Vue `{ [token]: Spy<T> }` for `global.provide` |
|
|
636
|
+
| `injectSpy(token)` _(Angular)_ / `injectSpy(moduleRef, token)` _(NestJS)_ | Inject typed as `Spy<T>` |
|
|
294
637
|
| `createFunctionSpy(name)` | A single standalone function spy with all helpers |
|
|
295
638
|
| `createObservableWithValues(configs, opts?)` | Build an Observable from value configs |
|
|
296
639
|
| `mockReadonlyProp` / `mockReadonlyPropGetter` / `mockAccessorsProp` | Mock readonly / accessor / signal props |
|
|
@@ -308,6 +651,38 @@ mockAccessorsProp(service, 'theme'); // spied get + set
|
|
|
308
651
|
|
|
309
652
|
`ValueConfig` (for `nextWithValues`): `{ value, delay? }` | `{ errorValue, delay? }` | `{ complete?, delay? }`.
|
|
310
653
|
|
|
654
|
+
## FAQ & troubleshooting
|
|
655
|
+
|
|
656
|
+
**"I get `X.nextWith is not a function` / observable helpers are missing."**
|
|
657
|
+
Import the rxjs layer once (e.g. in your test setup): `import 'vitest-auto-spy/rxjs';`. Without it,
|
|
658
|
+
requesting an observable spy throws a hint pointing you here.
|
|
659
|
+
|
|
660
|
+
**"My method isn't on the spy."**
|
|
661
|
+
Auto-discovery only sees **prototype methods**. Arrow-function class fields (`foo = () => {}`) and
|
|
662
|
+
plain properties aren't included — see [How it works](#how-it-works-and-what-it-wont-spy). List
|
|
663
|
+
getters/setters via `gettersToSpyOn` / `settersToSpyOn`.
|
|
664
|
+
|
|
665
|
+
**"Does it construct my class? Will the constructor's side effects run?"**
|
|
666
|
+
No. `createSpyFromClass` reads the prototype and never `new`s the class, so constructors (and their
|
|
667
|
+
HTTP/DB/`inject()` side effects) never run.
|
|
668
|
+
|
|
669
|
+
**"I only have an interface/type, not a class."**
|
|
670
|
+
Use [`createAutoMock<T>()`](#auto-mock-by-type-no-class-needed) — it builds the spy lazily from the
|
|
671
|
+
type via a `Proxy`, no runtime class needed.
|
|
672
|
+
|
|
673
|
+
**"Can I use it without TypeScript?"**
|
|
674
|
+
Yes — the runtime works in plain JS; you just lose the compile-time `Spy<T>` typing.
|
|
675
|
+
|
|
676
|
+
**"Native mock methods differ between runners."**
|
|
677
|
+
Only the auto-spy helpers are normalised. Native APIs stay the runner's own (`mockReturnValue` on
|
|
678
|
+
Vitest/Bun, `spy.method.mock.mockImplementation` on `node:test`).
|
|
679
|
+
|
|
680
|
+
## Versioning
|
|
681
|
+
|
|
682
|
+
This package follows [Semantic Versioning](https://semver.org). Breaking changes to the public API
|
|
683
|
+
land only in major releases; see the [Changelog](./CHANGELOG.md) for what changed in each version.
|
|
684
|
+
Releases are automated from Conventional Commits (see [Contributing](#contributing)).
|
|
685
|
+
|
|
311
686
|
## Contributing
|
|
312
687
|
|
|
313
688
|
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) and the
|
|
@@ -322,11 +697,18 @@ npm run build
|
|
|
322
697
|
|
|
323
698
|
Releases are automated: merging a PR into `master` bumps the version from the
|
|
324
699
|
Conventional Commit types and publishes to npm — see
|
|
325
|
-
[CONTRIBUTING.md →
|
|
700
|
+
[CONTRIBUTING.md → Releasing](./CONTRIBUTING.md#releasing).
|
|
326
701
|
|
|
327
702
|
If this package saved you time, a ⭐ on [GitHub](https://github.com/ASDAlexey/vitest-auto-spy)
|
|
328
703
|
helps others find it.
|
|
329
704
|
|
|
705
|
+
## Acknowledgements
|
|
706
|
+
|
|
707
|
+
API and ergonomics are modelled on Shai Reznik's
|
|
708
|
+
[`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spies) — `vitest-auto-spy` is its
|
|
709
|
+
Vitest-era successor with the same surface, so migrations are (mostly) a find-and-replace. Thanks to
|
|
710
|
+
the Vitest, Bun, RxJS and Angular communities whose tooling this builds on.
|
|
711
|
+
|
|
330
712
|
## License
|
|
331
713
|
|
|
332
714
|
[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;
|