Circle V2 API Docs
    Preparing search index...

    Testing

    Layer Tool Location
    Unit / Component Vitest + Testing Library Colocated *.test.ts / *.test.tsx
    DB Integration Vitest + real Supabase packages/db/src/**/*.test.ts
    E2E Playwright packages/e2e-web/src/features/

    Shared presets live in packages/vitest-config:

    For packages that don't need a DOM:

    // packages/vitest-config/src/base.ts
    export default defineConfig({
    test: {
    globals: true,
    environment: "node",
    include: ["src/**/*.test.{ts,tsx}"],
    coverage: {
    provider: "v8",
    reporter: ["text", "json", "html"],
    },
    },
    });

    For packages and apps that test React components:

    // packages/vitest-config/src/react.ts
    export default mergeConfig(baseConfig, defineConfig({
    plugins: [react({})],
    test: {
    environment: "jsdom",
    },
    }));

    The React setup file (@repo/vitest-config/setup-files/react.ts) registers:

    • @testing-library/jest-dom matchers
    • matchMedia mock
    • ResizeObserver mock

    Packages extend the shared presets in their own vitest.config.ts:

    // apps/web/vitest.config.ts
    import { mergeConfig } from "vitest/config";
    import react from "@repo/vitest-config/react";

    export default mergeConfig(react, {
    test: {
    include: ["**/*.test.{ts,tsx}"],
    setupFiles: ["@repo/vitest-config/setup-files/react"],
    alias: {
    "~": new URL("./", import.meta.url).pathname,
    "~tests": new URL("./__tests__", import.meta.url).pathname,
    },
    },
    });
    • File naming: *.test.ts for logic, *.test.tsx for components.
    • Placement: Colocated next to the source file they test.
    • Globals: describe, it, expect, vi are globally available (no imports needed).
    • Mocking: Use vi.mock() for module mocks.

    The web app provides a custom render function that wraps components in the ThemeProvider:

    // apps/web/__tests__/index.tsx
    import { render as rtlRender } from "@testing-library/react";
    import { ThemeProvider } from "@repo/ui/ThemeProvider";

    function render(ui: React.ReactElement, options = {}) {
    return rtlRender(ui, {
    wrapper: ({ children }) => <ThemeProvider>{children}</ThemeProvider>,
    ...options,
    });
    }

    export * from "@testing-library/react";
    export { render };

    Usage in tests:

    import { render, screen } from "~tests";

    describe("MyComponent", () => {
    it("renders correctly", () => {
    render(<MyComponent />);
    expect(screen.getByText("Hello")).toBeInTheDocument();
    });
    });

    Some packages export test utilities via a ./tests entry point so consumers can reuse mocks and helpers:

    import { mockTRPCContext } from "@repo/trpc/tests";
    import { mockThemeProvider } from "@repo/ui/tests";

    When multiple tests need the same data shape, create a factory — a function that returns a valid object with sensible defaults and accepts partial overrides.

    Location When to use Import style
    apps/web/__tests__/factories/ Entity used by 2+ modules, or a core domain entity (patient, user, org) import { makeFoo } from "~tests/factories"
    <module>/__tests__/ Entity only relevant within that module import { makeFoo } from "../__tests__/makeFoo"

    Start module-specific; promote to shared when a second module needs the same factory.

    • One file per entity: make<EntityName>.ts
    • Signature: make<Entity>(overrides?: Partial<T>): T — callers only specify the fields they care about.
    • Deep merging: For nested objects, define a typed overrides type (see MakeAuditRunByIdDataOverrides for an example).
    • Auto-incrementing IDs: Use a module-scoped sequence with a reset*Seq() export when tests need deterministic IDs.
    • Type safety: Always import the real types from @repo/db or @repo/trpc — no any.
    // apps/web/__tests__/factories/makePatient.ts
    import type { PatientId } from "@repo/db/schema";

    let seq = 1;
    export function resetPatientIdSeq() { seq = 1; }

    export function makePatient(overrides: Partial<Patient> = {}): Patient {
    const id = overrides.id ?? (`${seq++}` as PatientId);
    return {
    id,
    name: "Jane Doe",
    date_of_birth: "1990-01-01",
    ...overrides,
    };
    }

    For a real-world module-specific example, see apps/web/shared/audit-runs/__tests__/makeAuditRunByIdData.ts.

    DB tests in packages/db run against a real local Supabase instance.

    packages/db/__tests__/setup.ts handles:

    1. Loading dotenv/config for DATABASE_URL.
    2. Truncating all tables after each test (afterEach).
    // packages/db/vitest.config.ts
    export default mergeConfig(base, {
    test: {
    fileParallelism: false, // Tests run sequentially (shared DB)
    setupFiles: ["./__tests__/setup.ts"],
    },
    });
    # Start local Supabase (required)
    pnpm --filter @repo/db test:supabase-start

    # Run DB tests
    pnpm --filter @repo/db test
    • Repo methods work correctly (CRUD, filters, joins).
    • Error cases (not found, constraint violations).
    • Transactions behave as expected.

    End-to-end tests live in packages/e2e-web and use Playwright with Chromium.

    packages/e2e-web/
    ├── src/
    │ ├── features/ # Test specs organized by feature
    │ │ ├── auth/ # Login, signup, password flows
    │ │ ├── clients/ # Client management
    │ │ ├── templates/ # Template CRUD
    │ │ └── settings/ # Settings page
    │ ├── fixtures/ # Reusable test fixtures
    │ │ ├── controllers/ # Page object models
    │ │ ├── users/ # Test user factories
    │ │ └── data/ # Test data
    │ ├── lib/ # Utilities (DB, Supabase client, env)
    │ ├── global-setup.ts # Supabase health check, user creation
    │ └── global-teardown.ts # Cleanup
    ├── playwright.config.ts
    └── .env.test

    E2E tests use a fixture pattern with controllers (page objects) and test users:

    // Spec file
    test("can create a client", async ({ authenticatedPage }) => {
    await authenticatedPage.goto("/clients");
    // ...
    });
    # Start E2E Supabase instance
    pnpm --filter @repo/e2e-web test:supabase-start

    # Run E2E tests (starts web server automatically)
    START_WEB_SERVER=true pnpm --filter @repo/e2e-web test:e2e

    In CI, test results are reported to Currents for dashboard visibility. The config is in packages/e2e-web/currents.config.ts.

    Command What It Runs
    pnpm test All unit tests across the monorepo (via Turbo)
    pnpm test:watch All unit tests in watch mode
    pnpm --filter @repo/db test DB integration tests only
    pnpm --filter @repo/e2e-web test:e2e Playwright E2E tests
    pnpm --filter web test Web app unit tests only
    pnpm --filter @repo/ui test UI package tests only

    Turbo's test task depends on ^build, so dependencies are built before tests run. Test outputs are cached based on test file inputs and env vars.


    Next: CI/CD