Test with injected fetch
Inject an HttpFetch instead of replacing global fetch. This Vitest test exercises URL building, serialization, response shaping, and HTTP error handling entirely in memory.
import { expect, it } from "vitest";import { Http, rest, ServiceError } from "fetchzen";import type { HttpFetch } from "fetchzen";
it("creates a user and reports a failed request", async () => { const calls: { url: string; init?: RequestInit }[] = []; const fetch: HttpFetch = async (url, init) => { calls.push({ url, init }); const failed = calls.length > 1; return new Response( JSON.stringify(failed ? { message: "Unavailable" } : { id: "u1" }), { status: failed ? 503 : 201, headers: { "Content-Type": "application/json" }, }, ); }; const api = Http.create("https://api.test/v1", { preset: rest, cache: false, fetch, retry: 0, });
const result = await api.post<{ name: string }, { id: string }>("/users", { name: "Ana", });
expect(result.data).toEqual({ id: "u1" }); expect(result.status).toBe(201); expect(calls[0]?.url).toBe("https://api.test/v1/users"); expect(calls[0]?.init?.method).toBe("POST"); expect(calls[0]?.init?.body).toBe(JSON.stringify({ name: "Ana" })); expect(new Headers(calls[0]?.init?.headers).get("Content-Type")) .toBe("application/json");
await expect(api.get("/users")).rejects.toBeInstanceOf(ServiceError); expect(calls).toHaveLength(2);});Keep the fake realistic
Section titled “Keep the fake realistic”HttpFetchaccepts a URL string and optionalRequestInit, and returnsPromise<Response>. Construct a freshResponsefor each call: response bodies can only be consumed once.cache: falseprevents another test’s client for the same URL from retaining a different fetch or preset. Automatic retry is off by default;retry: 0makes that choice explicit here.- Include
Content-Typein mock responses: the defaultresponseType: "auto"uses response metadata to select parsing. This recipe usesrest, soresult.datais the full JSON body, not an unwrappeddatafield. - Without an injected fetch, the client resolves
globalThis.fetchat request time. - To test timeouts or cancellation, the fake must reject with
init.signal.reasonif the signal is already aborted, or listen for itsabortevent. A fake that ignores signals cannot demonstrate transport cancellation.
This test does not cover CORS, cookies, or a real server. Keep separate integration tests for those boundaries.
Related: shared typed client and single-flight token refresh.