npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vitest-auto-spy

v5.60.0

Published

Auto-generate fully-typed test spies from a class — across Vitest, Bun, node:test and Rstest, with Angular/NestJS/React/Vue/Svelte recipes. Drop-in replacement for jest-auto-spies and jasmine-auto-spies.

Readme

vitest-auto-spy

Typed test spies generated from a class, for Vitest, Bun, node:test and Rstest.

Pass a class and get a spy of every method, with helpers that match each method's return type. No hand-written vi.fn() per method, and the spy never drifts from the class. It is a drop-in replacement for jest-auto-spies and jasmine-auto-spies.

npm version downloads per day downloads per week downloads per month downloads over 18 months CI minzipped size types coverage license

Vitest Bun Angular on Bun node:test runtime deps

📚 Documentation · 🧭 Spec patterns · 📦 npm · 🐙 GitHub · 🔖 Changelog

🤖 AGENTS.md · 🔤 llms.txt · 📄 llms-full.txt — works with Claude Code, OpenAI Codex, GLM, Cursor, Copilot, Gemini CLI and the rest


What you get

  • A spy of every method from a class, typed, with helpers that match each return type: resolveWith for a Promise, nextWith for an Observable, mockReturnValue for the rest.
  • Answers per argument list: calledWith(1).mockReturnValue(...).
  • A spy from a type alone: createAutoMock<T>() and mockDeep<T>().
  • Angular TestBed in one line: provideAutoSpy(ApiService), then injectSpy(ApiService).
  • One API on every runner: Vitest, Bun, node:test and Rstest; helpers for Angular, NestJS, React, Vue / Pinia and Svelte.
  • Checks that make silent tests fail: strict mode, Observable assertions, cleanup between tests, 57 ESLint rules.
  • Zero runtime dependencies.

Contents

Reading this on npmjs.com? npm shows this file only up to Everything else. The documentation site has everything, and the whole README is on GitHub.

Install

npm i -D vitest-auto-spy

On Vitest, including Angular's ng test builder, you can import the helpers in a spec right away. If you spy on Observables, add import 'vitest-auto-spy/rxjs' once, usually in your setup file. For Bun, node:test, Rstest and Analog, see Installation → Wiring it up.

The plural name, vitest-auto-spies, is an alias that re-exports this package. Prefer the singular.

Quick start

UserService loads a user through ApiService. The spec replaces ApiService with a spy and tests the real UserService:

import { expect, it } from 'vitest';
import { createSpyFromClass } from 'vitest-auto-spy';

interface User {
  id: number;
  name: string;
}

class ApiService {
  async get(url: string): Promise<User> {
    const response = await fetch(url);
    return response.json();
  }
}

class UserService {
  constructor(private readonly api: ApiService) {}

  load(id: number): Promise<User> {
    return this.api.get(`/users/${id}`);
  }
}

it('loads the user from the API', async () => {
  const api = createSpyFromClass(ApiService); // every method is a spy; the constructor never runs
  api.get.resolveWith({ id: 1, name: 'Ada' }); // get() returns a Promise, so it has resolveWith

  const user = await new UserService(api).load(1);

  expect(user.name).toBe('Ada');
  expect(api.get).toHaveBeenCalledWith('/users/1');
});

An Angular component spec. CheckoutComponent injects CartService and shows its total:

import { TestBed } from '@angular/core/testing';
import { expect, it } from 'vitest';
import { injectSpy, provideAutoSpy, stable } from 'vitest-auto-spy/angular';

import { CartService } from './cart.service';
import { CheckoutComponent } from './checkout.component';

it('shows the cart total', async () => {
  TestBed.configureTestingModule({
    imports: [CheckoutComponent],
    providers: [provideAutoSpy(CartService)], // CartService is now a spy in TestBed
  });

  const cart = injectSpy(CartService); // typed Spy<CartService>, no cast
  cart.total.mockReturnValue(42); // configure before the component is created

  const fixture = TestBed.createComponent(CheckoutComponent);
  await stable(fixture); // runs change detection and effects, then waits

  expect(fixture.nativeElement.textContent).toContain('42');
});

Run it with npx vitest, or ng test on the Angular CLI. Step by step: Getting started.

Core API

createSpyFromClass

Creates a spy of every method of a class. Use it for any dependency that is a class.

import { createSpyFromClass } from 'vitest-auto-spy';

const cart = createSpyFromClass(CartService);
cart.total.mockReturnValue(42);

// or set answers up front, where the spy is built
const seeded = createSpyFromClass(CartService, { returns: { total: 42 } });

| Second argument | What it does | | ------------------------------------ | ------------------------------------------------------------------------ | | ['reload'] | an array: extra names to spy, besides the methods found on the prototype | | { onlyMethodsToSpyOn } | spies only these methods | | { instanceMethodsToSpyOn } | adds callables set in the constructor (signal(), arrow fields) | | { observablePropsToSpyOn } | Observable properties to spy (needs vitest-auto-spy/rxjs) | | { gettersToSpyOn, settersToSpyOn } | accessors to spy, under spy.accessorSpies | | { returns } | a return value per method, set up front | | { strict: true } | an unconfigured call throws |

Common mistake: an arrow-function field (load = () => {}) is not on the prototype, so it is not spied. List it in instanceMethodsToSpyOn.

Every option: createSpyFromClass.

provideAutoSpy and injectSpy

provideAutoSpy registers a spy of a class in Angular's TestBed; injectSpy reads it back typed as Spy<T>. NestJS has the same pair (vitest-auto-spy/nestjs); Vue has provideAutoSpy only.

import { injectSpy, provideAutoSpy } from 'vitest-auto-spy/angular';

TestBed.configureTestingModule({ providers: [provideAutoSpy(CartService)] });

const cart = injectSpy(CartService); // Spy<CartService>
cart.total.mockReturnValue(42);

provideAutoSpy takes the same second argument as createSpyFromClass.

Common mistake: calling injectSpy(X) for a class you did not provideAutoSpy. You get the real service; the library warns once and names the missing provider.

Full page: Angular.

calledWith and mustBeCalledWith

calledWith(...args) sets an answer for one argument list. mustBeCalledWith(...args) does the same and throws on any other arguments.

users.getName.calledWith(1).mockReturnValue('Ada');
users.getName(1); // 'Ada'
users.getName(2); // undefined

users.getName.mustBeCalledWith(1).mockReturnValue('Ada');
users.getName(2); // throws: … Wanted: getName(1) Actual: getName(2)

Matchers work too: calledWith(expect.any(Number)).

Common mistake: mockReturnValue and calledWith on the same method. The one set later replaces the other; the library warns. For a fallback answer, use the returns option; calledWith answers still win over it: createSpyFromClass(UserService, { returns: { getName: 'Unknown' } }).

Full page: Control helpers.

Return-type helpers

Each spied method gets helpers for what it returns:

| Method returns | Helpers | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | a value | mockReturnValue, calledWith(...), mustBeCalledWith(...), failWith(error) | | a Promise | resolveWith(value), rejectWith(error), resolveWithPerCall([...]) | | an Observable | nextWith(value) emits and keeps the stream open; nextOneTimeWith(value) emits once, then completes (like an HTTP call); nextWithValues([...]), throwWith(error), complete(), returnSubject() |

products.load.resolveWith([product]); // load(): Promise<Product[]>
products.items$.nextWith([product]); // items$(): Observable<Product[]>
payments.charge.calledWith(42).resolveWith({ ok: true }); // calledWith chains into every helper

The Observable helpers need one import per project, usually in the setup file (a spec works too): import 'vitest-auto-spy/rxjs';.

Common mistake: nextWith is not a function. The vitest-auto-spy/rxjs import is missing.

Full page: Control helpers.

createAutoMock and mockDeep

createAutoMock<T>() creates a spy from a TypeScript interface or type, when there is no class at run time. mockDeep<T>() does the same for nested objects.

import { createAutoMock, mockDeep } from 'vitest-auto-spy';

const gateway = createAutoMock<PaymentGateway>();
gateway.charge.resolveWith({ ok: true });

const api = mockDeep<ApiClient>();
api.users.find.resolveWith([user]); // nested members are spies too

Common mistake: reading a data property you did not seed. With only a type, every unknown member is a spy. Seed values: createAutoMock<Config>({ apiUrl: 'https://api.test' }).

Full page: Auto-mock by type.

strict

With strict: true, a call to a method you did not configure throws instead of returning undefined. Use it so a test cannot pass on an empty answer.

const users = createSpyFromClass(UserService, { strict: true });
users.load.resolveWith([]);
users.currentTenant(); // throws: UserService.currentTenant() was called; this strict double has nothing configured for it.

Turn it on for the whole suite with setupAutoSpy({ strict: true }) from vitest-auto-spy/setup.

Full page: Strict mode.

setupAutoSpy

setupAutoSpy() adds cleanup between tests to your setup file: it restores patched properties, checks for two copies of the library, and can catch leaked timers, network calls and console output.

// vitest.setup.ts
import { setupAutoSpy } from 'vitest-auto-spy/setup';

setupAutoSpy(); // or setupAutoSpy({ preset: 'strict' }) for every guard

Every option: Test-run hygiene.

Availability

Every entry point is published. Pick the one for your runner, and add a framework entry if you use one. The API is the same everywhere.

| You use | Import from | Guide | | ---------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------- | | Vitest | vitest-auto-spy | Vitest | | Bun (bun:test) | vitest-auto-spy/bun | Bun | | node:test | vitest-auto-spy/node | node:test | | Rstest | vitest-auto-spy/rstest | Rstest | | RxJS Observables | vitest-auto-spy/rxjs (import once) | RxJS | | Angular | vitest-auto-spy/angular | Angular | | Angular HttpClient | vitest-auto-spy/angular-http | Angular HTTP | | Angular router | vitest-auto-spy/angular-router | Angular router | | Angular signal forms | vitest-auto-spy/signal-forms | Signal forms | | Angular on Bun | vitest-auto-spy/bun-angular (a preload) | Angular on Bun | | NestJS | vitest-auto-spy/nestjs | NestJS | | React | vitest-auto-spy/react | React | | Vue / Pinia | vitest-auto-spy/vue | Vue | | Svelte | vitest-auto-spy/svelte | Svelte | | a jasmine-auto-spies suite | vitest-auto-spy/jasmine | Migrating from jasmine-auto-spies |

Other entry points hold utilities: /setup, /console, /dom-stubs, /diagnostics, /zone, /eslint-plugin and more. Full list: Installation → Entry points.

Versions and peer dependencies

Requirements

| Tool | Minimum | | ---------- | ---------------------------------------------------------------- | | Node.js | 22; CI runs 22, 24 and 26 | | Vitest | 2.1; one install covers 2.1 through 5.x | | Angular | 20 for the Angular entry points, 22 for /signal-forms | | Bun | 1.4 for vitest-auto-spy/bun-angular; any recent Bun for /bun | | TypeScript | 4.7 for the typed helpers; plain JavaScript works without types |

Vitest 5 needs no change on your side. Why each minimum is what it is: Compatibility.

The package ships ESM with bundled types. vitest-auto-spy/node and vitest-auto-spy/eslint-plugin also ship CommonJS.

Peer dependencies

Every peer is optional: install one only if you use the entry point that needs it. The package has no runtime dependencies.

| Peer | Needed for | Version | | --------------------------- | -------------------------------------------------------------------------------- | -------- | | vitest | the default entry vitest-auto-spy and every Vitest-only entry | >=2.1 | | rxjs | Observable spies from vitest-auto-spy/rxjs; rxjs 8 works too | >=7.2 | | @angular/core | vitest-auto-spy/angular and vitest-auto-spy/bun-angular | >=20 | | @angular/common | vitest-auto-spy/angular-http only | >=20 | | @angular/router | vitest-auto-spy/angular-router only | >=20 | | @angular/forms | vitest-auto-spy/signal-forms only; signal forms need Angular 22 | >=20 | | @angular/platform-browser | By and the directive matchers, and the Bun preload | >=20 | | @angular/compiler | vitest-auto-spy/angular/matchers and the vitest-auto-spy/bun-angular preload | >=20 | | @rstest/core | vitest-auto-spy/rstest only | >=0.11 |

A suite on vitest-auto-spy/bun or vitest-auto-spy/node does not need Vitest installed, and its types check without it.

How to mock

One short recipe per kind of dependency. Each links to the full page.

How to mock: a service behind Angular DI

import { injectSpy, provideAutoSpy } from 'vitest-auto-spy/angular';

TestBed.configureTestingModule({ providers: [provideAutoSpy(CartService)] });

const cart = injectSpy(CartService); // Spy<CartService>
cart.total.mockReturnValue(42);

Because provideAutoSpy reads the real class, a method you add to CartService appears on the spy too. A hand-written { provide: CartService, useValue: { total: vi.fn() } } stays out of date.

A primitive token is still fine as useValue: { provide: IS_BROWSER, useValue: false }. The lint rules no-mistyped-use-value and no-unknown-use-value-key check such values and keys, which Angular types as any.

On Vitest 4.1+, extendWithAutoSpies gives the spies as test fixtures:

import { of } from 'rxjs';
import { test as base } from 'vitest';
import { extendWithAutoSpies } from 'vitest-auto-spy/angular';

const test = extendWithAutoSpies(base, { cart: CartService, products: [ProductApi, { returns: { list: of([]) } }] });

test('checks out', ({ cart }) => cart.total.mockReturnValue(42));

It throws needs Vitest 4.1 or newer on an older Vitest. Details

How to mock: a service without DI

import { createAutoMock, createSpyFromClass } from 'vitest-auto-spy';

const cart = createSpyFromClass(CartService); // from a class
const gateway = createAutoMock<PaymentGateway>(); // from a type/interface alone

The constructor never runs. Use createAutoMock<T>() when there is no class at run time: an interface, a type alias, a .d.ts.

How to mock: an object the test already holds

createSpyFromInstance turns an existing object into a spy in place. Use it when other code already holds a reference to the object (a closure, a DI container, a subscription).

import { createSpyFromInstance, restoreSpiedInstance } from 'vitest-auto-spy';

const client = new PaymentsClient(config); // real, and already wired into the code under test
const spy = createSpyFromInstance(client); // same object, now typed as Spy<PaymentsClient>

spy.charge.calledWith(100).resolveWith({ ok: true });
await checkout.pay(100);

expect(spy.charge).toHaveBeenCalledWith(100);
restoreSpiedInstance(client); // client.charge is the real method again
  • It takes most of the options of createSpyFromClass.
  • With { passthrough: true }, each method keeps running its real code and records its calls, until you configure that method: createSpyFromInstance(TestBed.inject(CartService), { passthrough: true }).
  • To undo: restoreSpiedInstance(instance), restoreMockedProps(), or using spy = ….

Details

How to mock: a plain data fixture

createMock<T>(partial) builds typed test data from the fields you pass. Use it instead of an object literal with as T.

const device = { id: '1', name: 'TV', isOffline: false } as Device; // ❌ a key Device lacks still compiles
const device = createMock<Device>({ id: '1', name: 'TV' }); // ✅ checked at every depth

Unnamed fields are undefined, as with the cast, but an unknown key is a compile error. Where the value already sits in a typed slot (an argument, a typed const), just delete the cast. The prefer-create-mock lint rule finds the casts. Details

How to mock: reading a spy back from DI

const cart = injectSpy(CartService); // typed Spy<CartService>, no cast

Common mistake: vi.spyOn(TestBed.inject(CartService), 'total'). It replaces one method and leaves the rest of a real service running.

How to mock: a whole class's dependencies at once

createWithAutoSpies builds a class through Angular DI and spies every dependency you did not provide:

import { createWithAutoSpies } from 'vitest-auto-spy/angular';

const { instance, spies } = createWithAutoSpies(CartService);

spies.get(PricingService).total.mockReturnValue(100);
instance.checkout(3);

expect(spies.get(PricingService).total).toHaveBeenCalled();

Pass providers for the ones you want to control yourself; they win. Details

How to mock: a readonly property or a signal

import { mockReadonlyProp, mockValueProp, restoreMockedProps } from 'vitest-auto-spy';

mockReadonlyProp(store, 'items', signal([task])); // readonly field, signal(), computed()
mockValueProp(service, 'retries', 3); // a field the code assigns to

These helpers remember what they replaced, and one restoreMockedProps() undoes them all. setupAutoSpy() runs it after every test.

Common mistake: Object.defineProperty in a spec. Nothing undoes it, and under isolate: false it leaks into the next file. Details

How to mock: an Observable

import { expectEmission } from 'vitest-auto-spy';

cart.items$.nextWith([task]); // drive the stream from the spy

await expect(expectEmission(component.visible$)).resolves.toBe(true); // the first value, not a list

Common mistake: source$.subscribe(value => expect(value).toEqual(…)). If the stream never emits, nothing is asserted and the test passes. expectEmission fails on a silent stream. Details

How to mock: an overloaded method

A spy uses the last overload of a method by default. Name the one you mean on the declaration:

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

let shelves: Spy<ShelvesClient, { overload: { getShelf: 'first' } }>;

shelves = createSpyFromClass(ShelvesClient); // no second type argument here
shelves.getShelf.nextWith(page); // checked against the first signature

Common mistake: @ts-expect-error on the line. It also hides a fixture of the wrong shape. The no-ts-expect-error-on-double rule reports it. Details

How to mock: a call that has to throw

cart.checkout.failWith(new HttpErrorResponse({ status: 500 })); // every call throws
cart.checkout.calledWith(BAD_ID).failWith(new Error('unknown cart')); // only these arguments

failWith works on a method of any return type, on every runner. Details

How to mock: a promise a test forgets to await

// ❌ the test ends before the callback runs, so the assertion never runs at all
it('renders once compiled', () => {
  TestBed.compileComponents().then(() => expect(component.ready).toBe(true));
});

// ✅ the assertion lands inside the test
it('renders once compiled', async () => {
  await TestBed.compileComponents();

  expect(component.ready).toBe(true);
});

Under zone.js, an assertion that fails inside an un-awaited .then() does not even reach the runner. setupAutoSpy({ strayRejections: true }) makes it fail the test, and the no-floating-assertion lint rule finds the shape before it runs. Details

How to mock: a component's children

import { provideAutoSpy, renderShallow } from 'vitest-auto-spy/angular';

const { fixture, component } = renderShallow(TaskListComponent, {
  providers: [provideAutoSpy(TaskService)],
  inputs: { projectId: 42 },
});

renderShallow renders through the real TestBed without child components. fixture is a real ComponentFixture. keepTemplate and keepChildren keep parts of the template: Shallow component rendering.

How to mock: a child the template still binds

createComponentStub builds a stand-in child from the real one's selector, inputs and outputs, so it cannot drift:

import { createComponentStub } from 'vitest-auto-spy/angular';

const ChartStub = createComponentStub(ChartComponent);

TestBed.configureTestingModule({ imports: [DashboardComponent] });
TestBed.overrideComponent(DashboardComponent, { remove: { imports: [ChartComponent] }, add: { imports: [ChartStub] } });

const chart = fixture.debugElement.query(By.directive(ChartStub)).componentInstance;
expect(chart.series()).toEqual([1, 2, 3]);
chart.pointSelected.emit(2);

Directives and pipes work the same way. With renderShallow: { keepTemplate: true, keepChildren: [ChartStub] }. Details

How to mock: a lifecycle hook

Spy on the prototype, before the component is created:

const init = vi.spyOn(CardComponent.prototype, 'ngOnInit').mockImplementation(() => undefined);
const fixture = TestBed.createComponent(CardComponent);

fixture.detectChanges();
expect(init).toHaveBeenCalledTimes(1);

Common mistake: vi.spyOn(component, 'ngOnInit') on the instance. Angular never calls it, so the real hook runs. The no-instance-lifecycle-spy rule reports it. Better still, assert what the hook does.

How to mock: a class the code under test builds with new

createSpyClass is a real constructor whose instances are full spies:

import { createSpyClass, mockValueProp } from 'vitest-auto-spy';

const WorkerSpy = createSpyClass(BackgroundWorker);

mockValueProp(globalThis, 'BackgroundWorker', WorkerSpy);
service.start();

expect(WorkerSpy.calls[0]).toEqual(['./task.js']);
WorkerSpy.instances[0].postMessage.mockReturnValue(undefined);
  • calls holds the constructor arguments, instances the spy each call produced.
  • createSpyClass(BackgroundWorker, undefined, { statics: true }) also copies static members: functions become spies, data is copied, getters are skipped.
  • With no class at run time (a browser global, a vendor SDK), use mockConstructor, or stubConstructor(globalThis, 'Image', () => ({ src: '' })), which restoreMockedProps() removes.

Common mistake: vi.fn().mockImplementation(() => obj) called with new. An arrow function is not a constructor, so you get TypeError: … is not a constructor or an empty object. Details

How to mock: a double more than one spec uses

// ❌ a constant: one set of spies for the whole worker
export const actionContext = { actions: { navigateToSection: vi.fn() } };

// ✅ a factory: one set per caller
export const createActionContext = () => ({ actions: { navigateToSection: vi.fn() } });

Under isolate: false a module runs once per worker, so an exported constant is shared by every file. Put shared doubles in a *.mock.ts file, and never export from a spec file. The no-shared-module-level-mock rule finds these.

How to mock: a pipe

A pipe is a class with one method:

const currency = createSpyFromClass(CurrencyPipe);

currency.transform.calledWith(10, 'EUR').mockReturnValue('€10');

In a component spec, provide it with provideAutoSpy(CurrencyPipe), or drop it with renderShallow. To keep the template and replace the pipe in it: createComponentStub(CurrencyPipe, { transform: (value) => String(value) }).

How to mock: localStorage and sessionStorage

import { stubWebStorage } from 'vitest-auto-spy/dom-stubs';

const local = stubWebStorage('localStorage', { items: { token: 'abc' } });

session.logout();

expect(local.snapshot()).toEqual({});

An in-memory Storage, installed in beforeEach and removed by restoreMockedProps(). Details

How to mock: a Web Worker

import { stubWorker } from 'vitest-auto-spy/dom-stubs';

const workers = stubWorker<TreeRequest, TreeResponse>({
  respond: (request) => ({ requestId: request.requestId, nodes: [] }),
});

await firstValueFrom(service.visibleNodes(request));

expect(workers.last.messages).toEqual([request]);

respond answers each message on a microtask. Without it, drive the worker with workers.last.emit(data) / fail(error). Details

How to mock: fetch and other globals

import { mockValueProp } from 'vitest-auto-spy';
import { stubResponse } from 'vitest-auto-spy/setup';

beforeEach(() => {
  mockValueProp(
    globalThis,
    'fetch',
    vi.fn(async () => stubResponse({ body: user })),
  );
});
  • mockValueProp puts the original back after each test (through setupAutoSpy()).
  • stubResponse builds a real Response: data goes out as JSON (null included); undefined or no body sends no body. A body can be read once, so build a new one per call.
  • To only keep tests off the network, use blockNetwork().

Common mistake: global.fetch = vi.fn(). No cleanup reaches it, so it answers every later test of the file. The no-hand-assigned-global rule reports it. Details

How to mock: the console

import { useConsoleSpies } from 'vitest-auto-spy/console';

const { consoleErrorSpy } = useConsoleSpies();

it('reports a failed load', () => {
  service.load();

  expect(consoleErrorSpy).toHaveBeenCalledWith('load failed', expect.any(Error));
});

useConsoleSpies() installs silent spies before each test and restores the console after. It works on every runner. consoleOutput() returns everything logged, per channel, for one exact assertion.

Common mistakes:

  • A bare vi.spyOn(console, 'error') still prints; add .mockImplementation(() => undefined).
  • Relying on the import alone: it installs once per worker, and under isolate: false silences every later file.

setupAutoSpy({ strayConsole: 'throw' }) fails any test whose output nothing absorbed. Details

How to mock: a jasmine suite mid-migration

vitest-auto-spy/jasmine keeps jasmine's .and, .calls and .withArgs working, so changing the import is enough to run the suite:

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

const service: Spy<AccountService> = createSpyFromClass(AccountService);

service.load.and.returnValue('stubbed'); // .mockReturnValue(…) once the shim is gone
service.load.and.nextWith(account); // .nextWith(…) once the shim is gone
service.load.withArgs(7).and.returnValue('seven'); // .calledWith(7).mockReturnValue(…)
expect(service.load.calls.count()).toBe(1); // service.load.mock.calls.length

On bun test or node --test, call enableJasmineCompat() from vitest-auto-spy/jasmine-compat in the setup file instead. Spies created before that call do not get the jasmine API. npx vitest-auto-spy codemod --from jasmine rewrites the calls. Details

Everything else

npmjs.com shows a README only up to its first 64 kB, so this is where the npm page ends. Next:

The whole README, with its full table of contents, is on GitHub.