# Testing

## 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.

examples/docs/actors-testing/basic-setup.ts:

```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.

examples/docs/actors-testing/testing-state.ts:

```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:

examples/docs/actors-testing/testing-events.ts:

```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:

examples/docs/actors-testing/testing-schedules.ts:

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

const scheduler = actor({
  state: { completedTasks: [] as string[] },
  actions: {
    scheduleTask: async (c, taskName: string) => {
      await c.schedule.after(50, "completeTask", taskName);
    },
    completeTask: (c, taskName: string) => {
      c.state.completedTasks.push(taskName);
    },
    getCompletedTasks: (c) => c.state.completedTasks,
  },
});

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

test("scheduled work updates observable state", async (testCtx) => {
  const { client } = await setupTest(testCtx, registry);
  const handle = client.scheduler.getOrCreate(["test"]);

  await handle.scheduleTask("reminder");

  await expect
    .poll(() => handle.getCompletedTasks(), { timeout: 2_000, interval: 25 })
    .toContain("reminder");
});
```

Use a short schedule and `expect.poll` the action's observable result. Vitest's fake date and fake JavaScript timers do not advance RivetKit's scheduler, which runs outside the test's JavaScript timer queue.

## 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**: Poll observable state or output with a bounded timeout instead of sleeping for an exact duration.
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.
