Introduction
vitest-auto-spy reads a class and generates a typed spy for every method, powered by your test runner's mock primitive (vi.fn() on Vitest, and the equivalents on Bun and node:test). It is a drop-in successor to jest-auto-spies: the same API, but spying on Vitest-compatible runners instead of Jest.
Manually mocking a service is tedious and brittle — one vi.fn() line per method, kept in sync by hand. Instead:
import { createSpyFromClass, type Spy } from 'vitest-auto-spy';
let userService: Spy<UserService>;
beforeEach(() => {
userService = createSpyFromClass(UserService);
});Spy<UserService> exposes each method as a mock plus the right helpers based on the method's return type: resolveWith / rejectWith for Promises, nextWith / throwWith for RxJS Observables, and calledWith / mustBeCalledWith for argument matching.
No class to hand? Mock straight from a type or interface with createAutoMock<T>(), or build a recursive, self-seeding mock with mockDeep<T>(). For a double the code under test only reads — a DTO, a route snapshot, a config object — createMock<T>(partial?) returns a plain, spy-free T instead. See Auto-mock by type.
Where it runs
The core never imports your test runner directly: vi.fn() and its equivalents sit behind a MockAdapter that each entry point registers on import. Pick the one that matches your runner and the rest of the API is identical.
import { createSpyFromClass } from 'vitest-auto-spy'; // Vitest (default, zero-config)
import { createSpyFromClass } from 'vitest-auto-spy/bun'; // Bun — bun:test
import { createSpyFromClass } from 'vitest-auto-spy/node'; // node:testAngular's TestBed runs on Bun too, which nothing else offers — see Angular on Bun.
Next: Installation for the wiring, or createSpyFromClass for the full configuration surface.