Files
suaveplan/packages/testkit/testing/testing-dom/src/runner/runner.md
2026-05-27 23:47:53 +01:00

5.0 KiB

Runner — vitest / bun-test conditional re-exports

(no submodules)

- vitest — The `runner/vitest` module re-exports vite…
  • vitest — The runner/vitest module re-exports vitest's describe, it, test, expect, beforeAll, beforeEach, `afterAll…

  • vitest — The runner/vitest module re-exports vite…

  • vitest — The runner/vitest module re-exports vitest's describe, it, test, expect, beforeAll, beforeEach, `afterAll…

  • vitest — The runner/vitest module re-exports vite…

  • vitest — The runner/vitest module re-exports vitest's describe, it, test, expect, beforeAll, beforeEach, `afterAll…

  • vitest — The runner/vitest module re-exports vite…

  • vitest — The runner/vitest module re-exports vitest's describe, it, test, expect, beforeAll, beforeEach, `afterAll…

  • vitest — The runner/vitest module re-exports vitest's describe, it, test, expect, beforeAll, beforeEach, `afterAll…

Purpose

The runner barrel re-exports the test primitives (describe, it, test, expect, beforeAll, beforeEach, afterAll, afterEach, vi) plus a runner-neutral mock surface (fn, spy, stub, mock, fakeClock, restoreMocks, restoreTimers). Two backends are supported: vitest (the default) and bun's built-in bun:test. Which backend resolves at import time is driven by package.json conditional exports — under the bun condition Node resolves ./dist/runner/bun-test.js; under vitest or default it resolves ./dist/index.js which barrels in vitest.ts.

The mock helpers exist so test code can stay runner-agnostic. A package that uses fn() / spy() / stub() instead of importing vi directly will run unchanged when its consumers swap test runners — no test-source rewrites required. This is the pattern the original 2026-04-08-testing-libraries change established and we preserve it here.

Features

  • Re-exports of vitest primitives under the default / vitest conditions
  • bun:test re-exports under the bun condition with API-shaped equivalents (vi is aliased to bun's jest namespace)
  • Runner-neutral mock helpers: fn, spy, stub, mock(modulePath), fakeClock, restoreMocks, restoreTimers
  • mock(modulePath, factory?) returns a { restore() } handle for symmetry across runners
  • fakeClock() exists for legacy compatibility — new code should prefer the deterministic FakeClock class from @suaveplan/testing/clock/fake-clock

Usage

Basic example — runner-neutral test

import {
    describe,
    it,
    expect,
    fn,
    spy,
} from "@suaveplan/testing";

describe("user service", () => {
    it("calls the dependency exactly once", { timeout: 5000 }, () => {
        const dep = { fetch: () => "real" };
        const handle = spy(dep, "fetch");
        const callback = fn();
        callback("hello");
        expect(callback).toHaveBeenCalledWith("hello");
        expect(handle).toHaveBeenCalledTimes(0);
    });
});

Advanced example — module mocking with restore

import { mock, restoreMocks } from "@suaveplan/testing";

const handle = mock("./network.js", () => ({
    fetchUser: () => ({ id: 42 }),
}));

// run tests against the mocked module ...

handle.restore();
restoreMocks();

Switching to bun-test

In a consumer's package.json, set the bun condition (or run via bun test). Resolution picks @suaveplan/testing/dist/runner/bun-test.js automatically; no test-source changes are needed.

API Reference

Test primitives

describe, it, test, expect, beforeAll, beforeEach, afterAll, afterEach, vi — re-exported from the active runner.

Mock helpers

  • fn<TArgs, TReturn>(implementation?): Mock
  • spy<T, K>(obj: T, method: K): SpyHandle
  • stub<T, K>(obj: T, method: K, implementation?): SpyHandle
  • mock(modulePath: string, factory?: () => unknown): { restore() }
  • fakeClock(): { advance(ms), restore() }
  • restoreMocks(): void
  • restoreTimers(): void

Implementation Notes

  • vi on the bun path is aliased to jest. Bun-test's mock surface lives under jest for API compatibility; the alias keeps consumer code identical across runners.
  • fakeClock is legacy. Prefer the deterministic FakeClock class from the clock module — it's runner-independent, supports Clock injection across the package, and gives you snapshot() / restore() for fine control.
  • mock() on bun has a no-op restore() because bun:test does not expose unmock. Call restoreMocks() (which delegates to jest.restoreAllMocks()) to reset state.
  • The conditional-exports routing is load-bearing — see package.json#exports.".".bun and .vitest. Removing either branch breaks the dual-runner contract documented in the spec.
  • Type safety on cross-runner mocks is approximate. The wrappers use as never casts internally because the spy/stub generics differ between the two libraries; consumer code sees a stable shape.