Files
skills/docs/testkit/testing/testing-dom/src/runner/mocks.md
2026-05-29 08:20:56 +01:00

2.6 KiB

runner/mocks

Purpose

The runner/mocks module offers a small set of dependency-injection friendly mock helpers that work identically under vitest and bun-test. Because vi.mock / mock.module semantics differ sharply between the two runners (and often fail unpredictably in Bun's ESM resolver), this package prefers DI-style helpers — makeFn, makeClass, makePromise — that let a test substitute values at call-site rather than patching the module graph.

Features

  • Runner-agnostic helpers — no reliance on vi.mock / mock.module
  • makeFn returns a call-tracking function with .calls, .mock, and .mockReturnValueOnce
  • makeClass constructs a no-op subclass with overridable methods
  • makePromise returns { promise, resolve, reject } for deferred resolution in tests
  • Works inside both runners without configuration

Usage: Basic Example

import { makeFn } from "@suaveplan/testing-dom/runner";

const greet = makeFn<(name: string) => string>(() => "hi");
greet("alice");
expect(greet.calls).toEqual([["alice"]]);

Usage: Advanced Example

import { makeFn, makePromise } from "@suaveplan/testing-dom/runner";
import { describe, it, expect } from "@suaveplan/testing-dom/runner";

describe("flaky dependency via DI", () => {
    it("coordinates two async callers via a deferred", async () => {
        const deferred = makePromise<{ status: 200 }>();
        const fetchStub = makeFn<() => Promise<{ status: 200 }>>(
            () => deferred.promise
        );

        const results = [fetchStub(), fetchStub()];
        deferred.resolve({ status: 200 });

        expect(fetchStub.calls.length).toBe(2);
        expect((await Promise.all(results)).every((r) => r.status === 200)).toBe(true);
    });
});

API Reference

makeFn<TFn>(impl?): TFn & { calls: Parameters<TFn>[]; mockReturnValueOnce(v: ReturnType<TFn>): void }

makeClass<T>(overrides?): new () => T

makePromise<T>(): { promise: Promise<T>; resolve: (v: T) => void; reject: (e: unknown) => void }

Implementation Notes

These helpers intentionally duplicate some of what vi.fn offers because the project standard (captured in the bun-vitest-module-mocking feedback memory) is to avoid module-level mocks entirely in favour of DI. Module mocks break under Bun's ESM resolver in unpredictable ways, and even under vitest they create implicit coupling between the test and the subject's internal imports. makeFn records call history but does not provide toHaveBeenCalled — consumers use fn.calls.length directly so the assertion works under any runner.