Documentation Index
Fetch the complete documentation index at: https://confect.dev/llms.txt
Use this file to discover all available pages before exploring further.
@confect/test wraps convex-test 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
pnpm add -D @confect/test convex-test @effect/vitest
Setup
Create a TestConfect.ts file that configures the test layer with your database schema and Convex modules.
/// <reference types="vite/client" />
import { TestConfect as TestConfect_ } from "@confect/test";
import confectSchema from "./confect/schema";
export const TestConfect =
TestConfect_.TestConfect<typeof confectSchema>();
export const layer = TestConfect_.layer(
confectSchema,
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.
import { describe, it } from "@effect/vitest";
import { assertEquals } from "@effect/vitest/utils";
import { Effect } from "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.
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 for how to declare error schemas.
import { describe, it } from "@effect/vitest";
import { assertLeft } from "@effect/vitest/utils";
import { Effect } from "effect";
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.
import { GenericId } from "@confect/core";
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" });
}),
GenericId.GenericId("notes"),
);
});
When the handler returns void, the second argument can be omitted.
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.
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 endpoints.
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.
Effect.gen(function* () {
const c = yield* TestConfect.TestConfect;
yield* c.finishInProgressScheduledFunctions();
yield* c.finishAllScheduledFunctions(() => {
vi.advanceTimersByTime(5000);
});
});