Skip to content

vitest-auto-spyA typed spy for every method of a class

Pass a class and get back an object where every method is a spy, typed from the class, with helpers that match each return type. One API on Vitest, Bun, node:test and Rstest.

user.service.spec.ts
const users = createSpyFromClass(UserService);

users.load.resolveWith(user);
users.save.rejectWith(new HttpError(409));

users.load.mustBeCalledWith(7);
✓ 3 passed no casts · no vi.fn() kept in sync by hand

01 / The idea

Stop keeping a hand-written mock in sync ​

The mock you write today ​

ts
const users = {
  load: vi.fn(),
  save: vi.fn(),
  remove: vi.fn(),
} as unknown as UserService;

vi.mocked(users.load).mockResolvedValue(user);
vi.mocked(users.save).mockRejectedValue(new HttpError(409));

The line that replaces it ​

ts
import { type Spy, createSpyFromClass } from 'vitest-auto-spy';

const users: Spy<UserService> = createSpyFromClass(UserService);

users.load.resolveWith(user);
users.save.rejectWith(new HttpError(409));

The cast goes away. Because of the cast, renaming a method on UserService leaves the hand-written object compiling and the test passing, while it no longer tests anything. Spy<UserService> is typed from the class, so the same rename turns the spec red. The real constructor never runs, so the service's own dependencies need no mocks. No class to pass? createAutoMock<T>() builds the same spies from a type or an interface.

Getting started · createSpyFromClass · Control helpers

02 / Install

Install and import ​

bash
npm i -D vitest-auto-spy

Import from the entry point for your test runner. Everything after that line is the same.

ts
import { createSpyFromClass } from 'vitest-auto-spy'; // Vitest, no config
import { createSpyFromClass } from 'vitest-auto-spy/bun'; // bun:test
import { createSpyFromClass } from 'vitest-auto-spy/node'; // node:test
import { createSpyFromClass } from 'vitest-auto-spy/rstest'; // Rstest

The package has no runtime dependencies. rxjs and the @angular/* packages are needed only for their own entry points. Angular TestBed specs import provideAutoSpy and injectSpy from vitest-auto-spy/angular (Angular 20 or newer); createSpyFromClass works in any spec. See Installation.

03 / Slow tests

Which test is slow, and why ​

Vitest prints one Duration for the whole run. perf lists the spec files that are actually slow, re-measures each one alone, and shows where the CPU time went. Run it locally or in CI:

bash
npx vitest-auto-spy perf --gate
error  perf-gate-slow-file libs/player/src/lib/vod/vod.component.spec.ts
       The test bodies in this file add up to 9.20s, over the 5.00s budget (…). Re-measured on its own: 8.70s, still over budget.

       ┌─ measurements ────────────────────────────────────────────────
       │ tests             38   242ms each   20× the median test
       ├─ slowest tests ───────────────────────────────────────────────
       │  527ms  focus > moves through the controls
       ├─ where the time went · CPU profile, 8.41s sampled ────────────
       │ hooks        ███████████░░░░░░░░░ 54%   test bodies 46%
       │ by package   ██████░░░░░░░░░░░░░░  28%  jsdom
       │ in the spec  setUpWith 38%  ·  VodComponent_Template 17%  ·  assertFocus 8%
       ├─ likely cause ────────────────────────────────────────────────
       │ Most of the time is set-up that every test repeats: 54% is in hooks — setUpWith alone is 38%.
       └───────────────────────────────────────────────────────────────

The budget scales with the median test of the same run, so a fast laptop and a slow CI machine reach the same verdict. A file that was slow only because of its neighbours, and is fast when it runs alone, is reported as not reproduced, not as a failure. The profiler loads only for that re-measurement, so a normal run pays nothing. On Angular specs the card also splits time into TestBed setup, component creation, change detection and compilation. --code-quality writes the findings for the GitLab merge request widget.

The gate · perf in full · In CI

05 / Already have one

If a spy library is already in the repo ​

jest-auto-spies and jasmine-auto-spies have the same API. The codemod rewrites the imports and the calls, and shows the diff before anything is saved. Spectator, @testing-library/angular and Suites each have a page that maps their helpers to these, line by line.

What the others do differently · Migrate your tests · What is new in 4.0

Released under the MIT License.