# 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 ```ts import { makeFn } from "@suaveplan/testing-dom/runner"; const greet = makeFn<(name: string) => string>(() => "hi"); greet("alice"); expect(greet.calls).toEqual([["alice"]]); ``` ## Usage: Advanced Example ```ts 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(impl?): TFn & { calls: Parameters[]; mockReturnValueOnce(v: ReturnType): void }` ### `makeClass(overrides?): new () => T` ### `makePromise(): { promise: Promise; 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.