# vitest-auto-spy > Auto-generate fully-typed test spies from a class, a type or nothing at all — one API across > Vitest, `bun test` and `node:test`, plus Angular/NestJS/React/Vue/Svelte adapters. A drop-in > successor to `jest-auto-spies`. Zero runtime dependencies. - Install: `npm i -D vitest-auto-spy` - Repository: https://github.com/ASDAlexey/vitest-auto-spy - Documentation: https://asdalexey.github.io/vitest-auto-spy/ - Offline agent cheat sheet, shipped inside the package: `node_modules/vitest-auto-spy/AGENTS.md` - Everything below inlined as one file: https://asdalexey.github.io/vitest-auto-spy/llms-full.txt The one-line orientation: `createSpyFromClass(Class)` when a class exists, `createAutoMock()` when only a type does, `createMock(partial)` when the double is read rather than called. Each spied method earns helpers from its return type — `calledWith`/`mustBeCalledWith` always, `resolveWith`/`rejectWith` for a `Promise`, `nextWith`/`throwWith` for an `Observable`. --- # Overview A drop-in, fully-typed jest-auto-spies successor — one API across Vitest, Bun and node:test, and the only way to run Angular's TestBed under bun test. - **Fully typed spies** — Every method becomes a typed mock with return-type-aware helpers — resolveWith for Promises, nextWith for Observables, calledWith / mustBeCalledWith for argument matching. Overloaded methods included — a generated API client no longer forces the last signature, the one nobody calls. — https://asdalexey.github.io/vitest-auto-spy/core/create-spy-from-class - **Class, type, or neither** — createSpyFromClass reads a real class and spies every method. No class? createAutoMock() and recursive mockDeep() mock straight from a type — and createMock() builds the spy-free data shape a test only reads, from a deep partial that is still checked at every level. — https://asdalexey.github.io/vitest-auto-spy/core/auto-mock-by-type - **One API, three runtimes** — The core talks to your runner through a MockAdapter seam, so the same spies run on Vitest, Bun (bun:test) and node:test. Only native mock methods stay the runner's own. — https://asdalexey.github.io/vitest-auto-spy/runtimes/vitest - **Angular TestBed on Bun** — Bun ships no DOM and cannot resolve templateUrl, so Angular specs simply do not run there. One preload installs a DOM, inlines external templates and styles through a Bun.plugin hook, and boots a zoneless TestBed. — https://asdalexey.github.io/vitest-auto-spy/runtimes/bun-angular - **Signals a spec can drive** — mockSignalProp replaces a signal-valued property with a real WritableSignal and hands back the writable half, so a computed downstream recomputes and an effect runs. runEffect() runs one effect body on demand when its trigger is now static. — https://asdalexey.github.io/vitest-auto-spy/adapters/angular - **Resources that settle, or say why not** — Angular's httpResource() and resource() need a different wait each — measured on 21.2.17, an httpResource settles one tick plus one microtask after its response is flushed, a plain resource() takes two rounds, and neither has issued a request at all until something ticks. Get it wrong and nothing fails — the spec asserts the resource's default value and passes. settleResource() is the one wait both converge under, with a budget and a failure that names the resource and the flush it is missing. When the request is not the point at all, mockResourceProp drives a resource with no HTTP at all — set, fail and loading move it directly, so nothing is ever in flight. And toHaveResourceValue refuses to compare an unresolved resource even when its default happens to match — the assertion the whole family exists to stop passing. No library in the Angular world had an answer. — https://asdalexey.github.io/vitest-auto-spy/adapters/angular#resources-httpresource-and-resource - **Faster Angular specs** — renderShallow collapses the shallow-TestBed copy-paste into one call (1.7× on real component specs), createWithAutoSpies builds a class through DI with every unprovided token spied, and per-file diagnostics say which specs are worth converting. — https://asdalexey.github.io/vitest-auto-spy/adapters/angular - **The providers a testing module cannot reach** — overrideComponentProvider replaces a dependency a component declares in its own providers, and queues the component with the TestBed compiler so the override reaches it. assertNgModuleScopes names the module an AOT bundle stripped, createDirectiveHost compiles a host that is correct for both the compiler and the TestBed, and provideAutoSpyForToken covers a dependency behind an InjectionToken. — https://asdalexey.github.io/vitest-auto-spy/adapters/angular - **Observables that fail on silence** — expectEmission / expectEmissions / expectNoEmission replace the expect() inside a subscribe callback that never runs — the assertion is the await. Duck-typed, so no rxjs is pulled in. — https://asdalexey.github.io/vitest-auto-spy/core/observable-assertions - **When a green suite is lying** — zone.js replaces the global Promise, and a rejection nobody handled is drained into console.error and no further — it never reaches the channel Vitest listens on, so the runner is never told and the file still exits 0. An expect() inside a .then(), an async helper called without await, a TypeError thrown inside an import() in production code — every one of them is a passing test with a line of stderr behind it. The strayRejections option fails the test the rejection surfaced in, and the no-floating-assertion lint rule catches the commonest shape before it ever runs. One migrated suite of 11 587 tests, green, was hiding six real defects of that shape — two of them assertions that were simply false. — https://asdalexey.github.io/vitest-auto-spy/utilities/setup#_8-failing-on-a-rejection-zone-js-swallowed - **Doubles for what the code builds itself** — Vitest only forwards new to a constructible implementation, so the Jest idiom vi.fn(() => instance) records the call, skips the body and returns an empty object. mockConstructor / stubConstructor stay full runner mocks, collect instances, and refuse to be called without new. — https://asdalexey.github.io/vitest-auto-spy/utilities/constructor-doubles - **Waiting and the clock** — flushEventLoop and settleDynamicImport give a real event-loop turn while the timers stay faked, flushEventLoopUntil replaces a tuned sleep with a budgeted condition, and mockSystemTime / useCountingClock survive fake timers being re-installed around every test. Every wait here is bounded and names its own cause — including stable(fixture), which used to hand a stuck fixture to the runner's file-level timeout. — https://asdalexey.github.io/vitest-auto-spy/utilities/event-loop - **Fake timers that settle** — setupFakeTimers() pairs install with restore, and advanceTimers() drains the microtasks a bare advanceTimersByTime leaves pending — the gap that makes a timer assertion read like a race. betweenTests keeps the clock fake between the tests as well, which is what Jest fakeTimers.enableGlobally did and what a nested beforeAll needs. — https://asdalexey.github.io/vitest-auto-spy/utilities/fake-timers - **fakeAsync on Vitest** — zone.js/testing installs its ProxyZone through Jasmine and Jest hooks only, so fakeAsync and waitForAsync throw on Vitest. One import of vitest-auto-spy/zone patches them in — behind its own specifier, so a zoneless project never sees zone.js. One proxy zone serves the whole run, as Angular own jasmine patch does, so a component built in beforeEach and a tick() in the test share their timers. — https://asdalexey.github.io/vitest-auto-spy/utilities/zone - **Module mocks that prove they applied** — Under a bundler a vi.mock() with an unmatched specifier does nothing at all, and the spec asserts on the real module. assertMocked fails loudly instead, and moduleNamespace gives a factory the shape an interop probe recognises — no more "No default export is defined on the mock". — https://asdalexey.github.io/vitest-auto-spy/utilities/module-mocks - **Fixtures without casts** — narrow() says which branch of a union a test got, and prints the shape it actually had when it is wrong. withOverrides() builds a fixture from a model whose getters a spread would drop. asInstances() bridges a whole argument list at once, instead of one asInstance per tsc run. captureArg() takes hold of a callback or config the code under test built — expect.any tells you the kind, a captor hands you the value, which is the difference between "a callback was passed" and "call the callback that was passed". — https://asdalexey.github.io/vitest-auto-spy/utilities/fixtures - **The DOM the runner does not ship** — stubIntersectionObserver / stubResizeObserver / stubMutationObserver replace the global the code under test constructs and hand the spec the instance. stubMediaElement makes a