Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rjdellecese/confect/llms.txt

Use this file to discover all available pages before exploring further.

Confect provides a powerful testing package (@confect/test) built on top of convex-test that makes it easy to test queries, mutations, and actions in your application.

Installation

The testing package is included in the Confect monorepo:
npm install @confect/test convex-test

Setting Up Tests

The @confect/test package provides a TestConfect service that allows you to run your functions in a test environment.

Basic Test Setup

import { TestConfect } from "@confect/test";
import { Effect } from "effect";
import { describe, test } from "vitest";
import { databaseSchema } from "../confect/schema";
import { refs } from "../confect/_generated/refs";

const TestLayer = TestConfect.layer(
  databaseSchema,
  import.meta.glob("../convex/**/*.js")
)();

describe("My Tests", () => {
  test("example test", async () => {
    await Effect.gen(function* () {
      const t = yield* TestConfect();
      
      // Your test code here
    }).pipe(
      Effect.provide(TestLayer),
      Effect.runPromise
    );
  });
});

Testing Queries

Type-Safe Queries

Test queries with full type safety and schema validation

Fast Execution

Tests run against an in-memory database for speed

Query Testing Example

import { TestConfect } from "@confect/test";
import { Effect } from "effect";
import { refs } from "../confect/_generated/refs";

test("should fetch user by ID", async () => {
  await Effect.gen(function* () {
    const t = yield* TestConfect();
    
    // Insert test data
    yield* t.run(function* () {
      const db = yield* DatabaseWriter();
      yield* db.table("users").insert({
        name: "Alice",
        email: "alice@example.com"
      });
    });
    
    // Query the data
    const users = yield* t.query(
      refs.public.users.list,
      {}
    );
    
    expect(users).toHaveLength(1);
    expect(users[0].name).toBe("Alice");
  }).pipe(
    Effect.provide(TestLayer),
    Effect.runPromise
  );
});

Testing Mutations

Mutations can be tested using the mutation method on the TestConfect service.

Mutation Testing Example

test("should create a new user", async () => {
  await Effect.gen(function* () {
    const t = yield* TestConfect();
    
    // Call the mutation
    const userId = yield* t.mutation(
      refs.public.users.create,
      {
        name: "Bob",
        email: "bob@example.com"
      }
    );
    
    expect(userId).toBeDefined();
    
    // Verify the mutation worked
    const users = yield* t.query(
      refs.public.users.getById,
      { id: userId }
    );
    
    expect(users?.name).toBe("Bob");
  }).pipe(
    Effect.provide(TestLayer),
    Effect.runPromise
  );
});

Testing Actions

Actions can be tested similarly to mutations and queries.

Action Testing Example

test("should send email notification", async () => {
  await Effect.gen(function* () {
    const t = yield* TestConfect();
    
    const result = yield* t.action(
      refs.public.notifications.sendEmail,
      {
        to: "user@example.com",
        subject: "Test",
        body: "Test message"
      }
    );
    
    expect(result.success).toBe(true);
  }).pipe(
    Effect.provide(TestLayer),
    Effect.runPromise
  );
});

Running Custom Logic with run

The run method allows you to execute custom logic with full access to mutation services.
The run method provides access to DatabaseWriter, DatabaseReader, Scheduler, Storage, and Auth services.
test("should create multiple related records", async () => {
  await Effect.gen(function* () {
    const t = yield* TestConfect();
    
    yield* t.run(function* () {
      const db = yield* DatabaseWriter();
      const auth = yield* Auth();
      
      // Create a user
      const userId = yield* db.table("users").insert({
        name: "Charlie",
        email: "charlie@example.com"
      });
      
      // Create posts for the user
      yield* db.table("posts").insert({
        userId,
        title: "First Post",
        content: "Hello world!"
      });
    });
  }).pipe(
    Effect.provide(TestLayer),
    Effect.runPromise
  );
});

Testing with Authentication

You can test functions that require authentication using the withIdentity method.
test("should access user-specific data", async () => {
  await Effect.gen(function* () {
    const t = yield* TestConfect();
    
    // Test as an authenticated user
    const tWithAuth = t.withIdentity({
      subject: "user123",
      name: "Test User",
      email: "test@example.com"
    });
    
    const profile = yield* tWithAuth.query(
      refs.public.users.getMyProfile,
      {}
    );
    
    expect(profile).toBeDefined();
  }).pipe(
    Effect.provide(TestLayer),
    Effect.runPromise
  );
});

Testing Scheduled Functions

Confect supports testing scheduled functions and cron jobs.

finishInProgressScheduledFunctions

Complete all currently scheduled functions

finishAllScheduledFunctions

Complete all scheduled functions with timer advancement
test("should process scheduled tasks", async () => {
  await Effect.gen(function* () {
    const t = yield* TestConfect();
    
    // Schedule a function
    yield* t.mutation(
      refs.public.tasks.scheduleTask,
      { taskName: "cleanup", delayMs: 5000 }
    );
    
    // Finish in-progress scheduled functions
    yield* t.finishInProgressScheduledFunctions();
    
    // Verify the task was processed
    const tasks = yield* t.query(
      refs.public.tasks.list,
      { status: "completed" }
    );
    
    expect(tasks).toHaveLength(1);
  }).pipe(
    Effect.provide(TestLayer),
    Effect.runPromise
  );
});

Testing HTTP Routes

You can test HTTP endpoints using the fetch method.
test("should handle HTTP requests", async () => {
  await Effect.gen(function* () {
    const t = yield* TestConfect();
    
    const response = yield* t.fetch("/api/health");
    
    expect(response.status).toBe(200);
    
    const data = yield* Effect.promise(() => response.json());
    expect(data.status).toBe("ok");
  }).pipe(
    Effect.provide(TestLayer),
    Effect.runPromise
  );
});

Best Practices

Isolation

Each test gets a fresh database instance for complete isolation

Type Safety

Leverage TypeScript types for compile-time safety

Real Schema

Tests use your actual schema for realistic validation

Fast Feedback

In-memory execution provides instant test results

Test Organization

describe("Users API", () => {
  describe("Queries", () => {
    test("list users", async () => {
      // Test implementation
    });
    
    test("get user by ID", async () => {
      // Test implementation
    });
  });
  
  describe("Mutations", () => {
    test("create user", async () => {
      // Test implementation
    });
    
    test("update user", async () => {
      // Test implementation
    });
  });
});

Next Steps

API Reference

Explore the complete Confect API

Server Guide

Learn about server-side APIs