Testing patterns
Keep data access in one module
Section titled “Keep data access in one module”Put your queries behind small functions that take the client as an argument. Unit tests pass a stub; integration tests pass a real client pointed at a test project.
import type { VaifClient } from "@vaifstudio/client";
export type Post = { id: string; title: string; published: boolean };
export async function listPublished(vaif: VaifClient): Promise<Post[]> { const { data } = await vaif.from<Post>("posts").eq("published", true).orderBy("title").list(); return data;}
export async function createDraft(vaif: VaifClient, title: string): Promise<Post> { const { data } = await vaif.from<Post>("posts").create({ title, published: false }); return data;}Unit tests (Vitest)
Section titled “Unit tests (Vitest)”import { describe, it, expect, vi } from "vitest";import type { VaifClient } from "@vaifstudio/client";import { listPublished } from "../src/posts";
function stubClient(rows: unknown[]): VaifClient { const chain = { eq: vi.fn(() => chain), orderBy: vi.fn(() => chain), list: vi.fn(async () => ({ data: rows, count: rows.length })), }; return { from: vi.fn(() => chain) } as unknown as VaifClient;}
describe("listPublished", () => { it("returns the published posts", async () => { const vaif = stubClient([{ id: "1", title: "Hello", published: true }]); await expect(listPublished(vaif)).resolves.toHaveLength(1); expect(vaif.from).toHaveBeenCalledWith("posts"); });});Integration tests
Section titled “Integration tests”Run them against a separate project used only for tests, with its own keys. The secret key used here bypasses row rules, so keep it in your CI secrets and never in the repository:
import { describe, it, expect, afterAll } from "vitest";import { createVaifClient } from "@vaifstudio/client";import { createDraft } from "../src/posts";
const vaif = createVaifClient({ baseUrl: "https://api.vaif.studio", projectId: process.env.VAIF_TEST_PROJECT_ID!, apiKey: process.env.VAIF_TEST_SECRET_KEY!,});
const created: string[] = [];
describe("posts (integration)", () => { it("creates and reads back a draft", async () => { const draft = await createDraft(vaif, "Integration test"); created.push(draft.id); const { data } = await vaif.from("posts").get(draft.id); expect(data.title).toBe("Integration test"); });
afterAll(async () => { for (const id of created) await vaif.from("posts").delete(id); });});Test row rules
Section titled “Test row rules”Row rules apply to the user’s access token, so test them as a user: sign in as two test users, write a row as one, and check that the other cannot read it. This example assumes a notes table with an owner rule (see Row rules).
import { createVaifClient } from "@vaifstudio/client";
async function clientFor(email: string, password: string) { let token = ""; const vaif = createVaifClient({ baseUrl: "https://api.vaif.studio", projectId: process.env.VAIF_TEST_PROJECT_ID, apiKey: process.env.VAIF_TEST_PUBLISHABLE_KEY, accessToken: () => token, }); const result = await vaif.auth.login(email, password); if ("accessToken" in result) token = result.accessToken; return vaif;}
const alice = await clientFor("alice@test.example", process.env.ALICE_PASSWORD!);const bob = await clientFor("bob@test.example", process.env.BOB_PASSWORD!);
const { data: note } = await alice.from("notes").create({ body: "private" });const { data: seenByBob } = await bob.from("notes").eq("id", String(note.id)).list();if (seenByBob.length !== 0) throw new Error("row rule is not applied");CI (GitHub Actions)
Section titled “CI (GitHub Actions)”name: Testson: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm test - if: github.ref == 'refs/heads/main' run: npm run test:integration env: VAIF_TEST_PROJECT_ID: ${{ secrets.VAIF_TEST_PROJECT_ID }} VAIF_TEST_SECRET_KEY: ${{ secrets.VAIF_TEST_SECRET_KEY }}- Never point tests at your production project.
- Clean up what a test creates.
- Keep keys in your CI provider’s secrets, never in the repository.