> ## Documentation Index
> Fetch the complete documentation index at: https://confect.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Test your Confect functions without a live Convex backend.

`@confect/test` wraps [`convex-test`](https://docs.convex.dev/testing) with Effect-native APIs. It provides a `TestConfect` service that lets you call your Confect functions using refs, run arbitrary mutation-context code for test setup, and test with user identities.

## Installation

<CodeGroup>
  ```bash pnpm theme={null}
  pnpm add -D @confect/test convex-test @effect/vitest
  ```

  ```bash npm theme={null}
  npm install -D @confect/test convex-test @effect/vitest
  ```

  ```bash yarn theme={null}
  yarn add -D @confect/test convex-test @effect/vitest
  ```

  ```bash bun theme={null}
  bun add -D @confect/test convex-test @effect/vitest
  ```
</CodeGroup>

## Setup

Create a `TestConfect.ts` file that configures the test layer with your generated runtime schema, generated Convex schema definition, and Convex modules. Both schemas are emitted by `confect codegen` from `confect/tables/*.ts`; you never edit them by hand.

```ts test/TestConfect.ts theme={null}
/// <reference types="vite/client" />

import { TestConfect as TestConfect_ } from "@confect/test";

import confectSchema from "./confect/_generated/schema";
import convexSchema from "./confect/_generated/convexSchema";

export const TestConfect = TestConfect_.TestConfect<typeof confectSchema>();

export const layer = TestConfect_.layer(
  confectSchema,
  convexSchema,
  import.meta.glob("./convex/**/!(*.*.*)*.*s"),
);
```

Every time the `TestConfect` layer is constructed, a new test database is created. If you want a clean database for each test, provide the layer on a per-test basis (as shown below). If you want to share state across tests in a describe block, you can provide the layer at a higher scope instead.

## Writing tests

Use `@effect/vitest`'s `it.effect` to write effectful tests. Yield `TestConfect` from the service, then call your functions using refs.

```ts test/notes.test.ts theme={null}
import { describe, it } from "@effect/vitest";
import { assertEquals } from "@effect/vitest/utils";
import * as Effect from "effect/Effect";

import refs from "./confect/_generated/refs";
import * as TestConfect from "./TestConfect";

describe("notes", () => {
  it.effect("can insert and list notes", () =>
    Effect.gen(function* () {
      const c = yield* TestConfect.TestConfect;

      yield* c.mutation(refs.public.notes.insert, {
        text: "Hello",
      });

      const notes = yield* c.query(refs.public.notes.list, {});

      assertEquals(notes.length, 1);
      assertEquals(notes[0]?.text, "Hello");
    }).pipe(Effect.provide(TestConfect.layer())),
  );
});
```

## Calling functions

`TestConfect` provides `query`, `mutation`, and `action` methods that accept a ref and args, returning an `Effect` with the decoded result.

```ts theme={null}
Effect.gen(function* () {
  const c = yield* TestConfect.TestConfect;

  const notes = yield* c.query(refs.public.notes.list, {});

  const noteId = yield* c.mutation(refs.public.notes.insert, {
    text: "Hello",
  });

  const result = yield* c.action(refs.public.random.getNumber, {});
});
```

## Typed errors

When a ref's spec declares an `error` schema, the decoded error is added to the error channel of `query`, `mutation`, and `action` alongside `ParseError`. See [Error Handling](/server/error-handling) for how to declare error schemas.

```ts theme={null}
import { describe, it } from "@effect/vitest";
import { assertLeft } from "@effect/vitest/utils";
import type { GenericId } from "convex/values";
import * as Effect from "effect/Effect";

import { NoteNotFound } from "./confect/notes.spec";
import refs from "./confect/_generated/refs";
import * as TestConfect from "./TestConfect";

describe("notes", () => {
  it.effect("returns NoteNotFound for a missing id", () =>
    Effect.gen(function* () {
      const c = yield* TestConfect.TestConfect;

      const result = yield* c
        .query(refs.public.notes.getOrFail, {
          noteId: "missing" as GenericId<"notes">,
        })
        .pipe(Effect.either);

      assertLeft(result, new NoteNotFound({ noteId: "missing" }));
    }).pipe(Effect.provide(TestConfect.layer())),
  );
});
```

## Running setup code

Use `run` to execute arbitrary code with mutation-context services (like `DatabaseWriter`) for test setup. When the handler returns a value, pass a `Schema` as the second argument for decoding.

```ts theme={null}
import { Id } from "./confect/_generated/id";
import { DatabaseWriter } from "./confect/_generated/services";

Effect.gen(function* () {
  const c = yield* TestConfect.TestConfect;

  const noteId = yield* c.run(
    Effect.gen(function* () {
      const writer = yield* DatabaseWriter;

      return yield* writer.table("notes").insert({ text: "seed data" });
    }),
    Id("notes"),
  );
});
```

When the handler returns `void`, the second argument can be omitted.

```ts theme={null}
Effect.gen(function* () {
  const c = yield* TestConfect.TestConfect;

  yield* c.run(
    Effect.gen(function* () {
      const writer = yield* DatabaseWriter;

      yield* Effect.forEach(items, (item) =>
        writer.table("notes").insert(item),
      );
    }),
  );
});
```

## Testing with user identity

Use `withIdentity` to scope function calls to a specific user identity.

```ts theme={null}
Effect.gen(function* () {
  const c = yield* TestConfect.TestConfect;

  const asUser = c.withIdentity({
    subject: "user-123",
    tokenIdentifier: "https://example.com|user-123",
    email: "user@example.com",
  });

  const identity = yield* asUser.query(refs.public.auth.getIdentity, {});
});
```

## Testing HTTP endpoints

Use `fetch` to test your [HTTP API](/server/http-api) endpoints.

```ts theme={null}
Effect.gen(function* () {
  const c = yield* TestConfect.TestConfect;

  const response = yield* c.fetch("/api/get-first");
  const body = yield* Effect.promise(() => response.json());
});
```

## Testing scheduled functions

`TestConfect` provides helpers for advancing scheduled functions in tests.

```ts theme={null}
Effect.gen(function* () {
  const c = yield* TestConfect.TestConfect;

  yield* c.finishInProgressScheduledFunctions();

  yield* c.finishAllScheduledFunctions(() => {
    vi.advanceTimersByTime(5000);
  });
});
```
