supabase-js queries and auth tested with a mocked fetch (Vitest): working versions (tested 2026-10-15)
Use @supabase/supabase-js for table queries and auth in app code, and unit-test it offline by passing a fake fetch to createClient.
passed4 passed · 0 failed2026-10-15 13:34 UTC- Status
- passed
- Last passed
- 2026-10-15 13:34 UTC
- Tests
- 4 / 4
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38742087302
The code
Exactly the files that passed. Your agent can pull them with get_recipe("supabase-js-mocked-fetch") (install), or see them on GitHub.
{
"name": "supabase-js-mocked-fetch",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run --reporter=default --reporter=json --outputFile=test-report.json"
},
"dependencies": {
"@supabase/supabase-js": "2.117.3"
},
"devDependencies": {
"@types/node": "24.19.2",
"vitest": "5.0.4"
}
}
import type { SupabaseClient } from "@supabase/supabase-js";
export type Todo = { id: number; title: string; done: boolean; user_id: string };
export async function openTodos(sb: SupabaseClient, userId: string): Promise<Todo[]> {
const { data, error } = await sb.from("todos").select("id,title,done,user_id").eq("user_id", userId).eq("done", false).order("id");
if (error) throw new Error(error.message);
return data as Todo[];
}
export async function addTodo(sb: SupabaseClient, userId: string, title: string): Promise<Todo> {
const { data, error } = await sb.from("todos").insert({ title, user_id: userId }).select().single();
if (error) throw new Error(error.message);
return data as Todo;
}
export async function signIn(sb: SupabaseClient, email: string, password: string) {
const { data, error } = await sb.auth.signInWithPassword({ email, password });
if (error) throw error;
return data.user?.id;
}
import { describe, it, expect, vi } from "vitest";
import { createClient } from "@supabase/supabase-js";
import { addTodo, openTodos, signIn } from "../src/todos";
const URL_ = "https://demo.supabase.co";
function client(handler: (url: URL, init: RequestInit) => Response) {
const fetch = vi.fn(async (input: RequestInfo | URL, init: RequestInit = {}) => handler(new URL(String(input instanceof Request ? input.url : input)), init));
const sb = createClient(URL_, "anon-key", { global: { fetch: fetch as any }, auth: { persistSession: false, autoRefreshToken: false } });
return { sb, fetch };
}
const ok = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
describe("todos", () => {
it("builds the PostgREST filter query", async () => {
const { sb, fetch } = client(() => ok([{ id: 1, title: "a", done: false, user_id: "u1" }]));
expect(await openTodos(sb, "u1")).toHaveLength(1);
const url = new URL(String(fetch.mock.calls[0][0]));
expect(url.pathname).toBe("/rest/v1/todos");
expect(url.searchParams.get("user_id")).toBe("eq.u1");
expect(url.searchParams.get("done")).toBe("eq.false");
});
it("insert + single returns the row", async () => {
const { sb, fetch } = client(() => ok({ id: 2, title: "b", done: false, user_id: "u1" }, 201));
expect((await addTodo(sb, "u1", "b")).id).toBe(2);
expect(fetch.mock.calls[0][1]?.method).toBe("POST");
});
it("surfaces PostgREST errors", async () => {
const { sb } = client(() => ok({ code: "42501", message: "permission denied for table todos", details: null, hint: null }, 403));
await expect(openTodos(sb, "u1")).rejects.toThrow(/permission denied/);
});
it("signs in through GoTrue", async () => {
const { sb, fetch } = client((url) => {
expect(url.pathname).toBe("/auth/v1/token");
expect(url.searchParams.get("grant_type")).toBe("password");
return ok({ access_token: "at", token_type: "bearer", expires_in: 3600, expires_at: Math.floor(Date.now() / 1000) + 3600, refresh_token: "rt", user: { id: "u1", aud: "authenticated", app_metadata: {}, user_metadata: {}, created_at: new Date().toISOString() } });
});
expect(await signIn(sb, "[email protected]", "pw")).toBe("u1");
expect(fetch).toHaveBeenCalledTimes(1);
});
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Astro 7 component rendering tests with the Container API (Vitest)
Render Astro components to HTML in Vitest with the Container API and getViteConfig, checking props, slots and conditional markup.
AWS SDK v3 DynamoDB document client and SQS events, tested with Vitest matchers
A single-table DynamoDB order service on @aws-sdk/lib-dynamodb with conditional puts, begins_with queries and optimistic locking, publishing SQS events, tested offline with aws-sdk-client-mock and aws-sdk-client-mock-vitest matchers.
Effect 4 Schema classes, typed errors and retries, tested with @effect/vitest
Effect 4 Schema.Class models with checks, brands, FiniteFromString and fromJsonString for decode and encode, TaggedError failures, and Effect.retry with exponential backoff verified on the TestClock using @effect/vitest.
Hono + Drizzle ORM + SQLite REST API (Vitest)
CRUD REST API in Hono backed by Drizzle ORM on better-sqlite3, with Zod validation, tested with Vitest and app.request().