# Testing

> Source: `src/content/docs/actors/testing.mdx`
> Canonical URL: https://rivet.dev/docs/actors/testing
> Description: Rivet provides a straightforward testing framework to build reliable and maintainable applications. This guide covers how to write effective tests for your actor-based services.

---
## Setup

To set up testing with Rivet:

```bash
# Install Vitest
npm install -D vitest

# Run tests
npm test
```

## Basic Testing Setup

Rivet includes a test helper called `setupTest` that starts your registry in test mode and returns a client connected to it. This allows for fast, isolated tests without external dependencies.

```ts
import { test, expect } from "vitest";
import { setupTest } from "rivetkit/test";
import { actor, setup } from "rivetkit";

// Define the actor
const myActor = actor({
  state: { value: "initial" },
  actions: {
    someAction: (c) => {
      c.state.value = "updated";
      return c.state.value;
    },
    getState: (c) => {
      return c.state.value;
    }
  }
});

// Create the registry
const registry = setup({
  use: { myActor }
});

// Test the actor
test("my actor test", async (testCtx) => {
  const { client } = await setupTest(testCtx, registry);

  // Now you can interact with your actor through the client
  const myActorHandle = client.myActor.getOrCreate(["test"]);

  // Test your actor's functionality
  await myActorHandle.someAction();

  // Make assertions
  const result = await myActorHandle.getState();
  expect(result).toEqual("updated");
});
```

## Testing Actor State

State persists within each test, allowing you to verify that your actor correctly maintains state between operations.

```ts
import { test, expect } from "vitest";
import { setupTest } from "rivetkit/test";
import { actor, setup } from "rivetkit";

// Define the counter actor
const counter = actor({
  state: { count: 0 },
  actions: {
    increment: (c) => {
      c.state.count += 1;
      c.broadcast("newCount", c.state.count);
      return c.state.count;
    },
    getCount: (c) => {
      return c.state.count;
    }
  }
});

// Create the registry
const registry = setup({
  use: { counter }
});

// Test state persistence
test("actor should persist state", async (testCtx) => {
  const { client } = await setupTest(testCtx, registry);
  const counterHandle = client.counter.getOrCreate(["test"]);

  // Initial state
  expect(await counterHandle.getCount()).toBe(0);

  // Modify state
  await counterHandle.increment();

  // Verify state was updated
  expect(await counterHandle.getCount()).toBe(1);
});
```

## Testing Events

For actors that emit events, you can verify events are correctly triggered by subscribing to them:

```ts
import { test, expect, vi } from "vitest";
import { setupTest } from "rivetkit/test";
import { actor, setup } from "rivetkit";

interface ChatMessage {
  username: string;
  message: string;
}

// Define the chat room actor
const chatRoom = actor({
  state: {
    messages: [] as ChatMessage[]
  },
  actions: {
    sendMessage: (c, username: string, message: string) => {
      c.state.messages.push({ username, message });
      c.broadcast("newMessage", username, message);
    },
    getHistory: (c) => {
      return c.state.messages;
    },
  },
});

// Create the registry
const registry = setup({
  use: { chatRoom }
});

// Test event emission
test("actor should emit events", async (testCtx) => {
  const { client } = await setupTest(testCtx, registry);
  const chatRoomHandle = client.chatRoom.getOrCreate(["test"]);

  // Set up event handler with a mock function
  const mockHandler = vi.fn();
  const conn = chatRoomHandle.connect();
  conn.on("newMessage", mockHandler);

  // Trigger the event
  await conn.sendMessage("testUser", "Hello world");

  // Wait for the event to be emitted
  await vi.waitFor(() => {
    expect(mockHandler).toHaveBeenCalledWith("testUser", "Hello world");
  });
});
```

## Testing Schedules

Rivet's schedule functionality can be tested by scheduling work and waiting for it to run:

```ts
import { test, expect } from "vitest";
import { setupTest } from "rivetkit/test";
import { actor, setup } from "rivetkit";

// Helper to wait for a delay
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

// Define the scheduler actor
const scheduler = actor({
  state: {
    tasks: [] as string[],
    completedTasks: [] as string[]
  },
  actions: {
    scheduleTask: (c, taskName: string, delayMs: number) => {
      c.state.tasks.push(taskName);
      // Schedule "completeTask" to run after the specified delay
      c.schedule.after(delayMs, "completeTask", taskName);
      return { success: true };
    },
    completeTask: (c, taskName: string) => {
      // This action will be called by the scheduler when the time comes
      c.state.completedTasks.push(taskName);
      return { completed: taskName };
    },
    getCompletedTasks: (c) => {
      return c.state.completedTasks;
    }
  }
});

// Create the registry
const registry = setup({
  use: { scheduler }
});

// Test scheduled tasks
test("scheduled tasks should execute", async (testCtx) => {
  const { client } = await setupTest(testCtx, registry);
  const schedulerHandle = client.scheduler.getOrCreate(["test"]);

  // Set up a scheduled task
  await schedulerHandle.scheduleTask("reminder", 100); // 100ms in the future

  // Wait for the scheduled task to run
  await wait(150);

  // Verify the scheduled task executed
  expect(await schedulerHandle.getCompletedTasks()).toContain("reminder");
});
```

Use a short real-time delay to wait for scheduled work to run. `setupTest` does not install fake timers, so if you want to use `vi.useFakeTimers()` you must enable it yourself and confirm it works with your selected runtime.

## Best Practices

1. **Isolate tests**: Each test should run independently, avoiding shared state.
2. **Test edge cases**: Verify how your actor handles invalid inputs, concurrent operations, and error conditions.
3. **Test scheduled operations**: Use short real-time delays to wait for scheduled work to run.
4. **Use realistic data**: Test with data that resembles production scenarios.

`setupTest` starts the registry and disposes the returned client when the test finishes, so you can focus on writing effective tests for your business logic.

## API Reference

- [`setupTest`](/typedoc/functions/rivetkit.test_mod.setupTest.html) - Test setup helper function

_Source doc path: /docs/actors/testing_
