Skip to content

Commit c9f072b

Browse files
committed
docs: add testing docs (#801)
1 parent ed5f01a commit c9f072b

File tree

3 files changed

+256
-1
lines changed

3 files changed

+256
-1
lines changed

docs/changelog/overview.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ mode: "center"
99
Releases](https://github.com/rivet-gg/actor-core/releases) page.
1010
</Info>
1111

12+
<Update description="0.7.6" label="March 26th, 2025">
13+
## Features
14+
15+
- [**Testing with Vitest**](/concepts/testing): Write unit tests for your actors with [Vitest](https://vitest.dev/)
16+
17+
## Fixes
18+
19+
- Using `vars` and `state` in the same actor sometimes causes `unknown` types
20+
</Update>
21+
1222
<Update description="0.7.5" label="March 25th, 2025">
1323
## Features
1424

docs/concepts/testing.mdx

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
title: Testing
3+
icon: vial-circle-check
4+
---
5+
6+
ActorCore provides a straightforward testing framework to build reliable and maintainable applications. This guide covers how to write effective tests for your actor-based services.
7+
8+
## Setup
9+
10+
To set up testing with ActorCore:
11+
12+
```bash
13+
# Install Vitest
14+
npm install -D vitest
15+
16+
# Run tests
17+
npm test
18+
```
19+
20+
## Basic Testing Setup
21+
22+
ActorCore includes a test helper called `setupTest` that configures a test environment with in-memory drivers for your actors. This allows for fast, isolated tests without external dependencies.
23+
24+
<CodeGroup>
25+
```ts tests/my-actor.test.ts
26+
import { test, expect } from "vitest";
27+
import { setupTest } from "actor-core/test";
28+
import { app } from "../src/index";
29+
30+
test("my actor test", async () => {
31+
const { client } = await setupTest(app);
32+
33+
// Now you can interact with your actor through the client
34+
const myActor = await client.myActor.get();
35+
36+
// Test your actor's functionality
37+
await myActor.someAction();
38+
39+
// Make assertions
40+
const result = await myActor.getState();
41+
expect(result).toEqual("updated");
42+
});
43+
```
44+
45+
```ts src/index.ts
46+
import { actor, setup } from "actor-core";
47+
48+
const myActor = actor({
49+
state: { value: "initial" },
50+
actions: {
51+
someAction: (c) => {
52+
c.state.value = "updated";
53+
return c.state.value;
54+
},
55+
getState: (c) => {
56+
return c.state.value;
57+
}
58+
}
59+
});
60+
61+
export const app = setup({
62+
actors: { myActor }
63+
});
64+
65+
export type App = typeof app;
66+
```
67+
</CodeGroup>
68+
69+
## Testing Actor State
70+
71+
The test framework uses in-memory drivers that persist state within each test, allowing you to verify that your actor correctly maintains state between operations.
72+
73+
<CodeGroup>
74+
```ts tests/counter.test.ts
75+
import { test, expect } from "vitest";
76+
import { setupTest } from "actor-core/test";
77+
import { app } from "../src/index";
78+
79+
test("actor should persist state", async () => {
80+
const { client } = await setupTest(app);
81+
const counter = await client.counter.get();
82+
83+
// Initial state
84+
expect(await counter.getCount()).toBe(0);
85+
86+
// Modify state
87+
await counter.increment();
88+
89+
// Verify state was updated
90+
expect(await counter.getCount()).toBe(1);
91+
});
92+
```
93+
94+
```ts src/index.ts
95+
import { setup } from "actor-core";
96+
97+
const counter = actor({
98+
state: { count: 0 },
99+
actions: {
100+
increment: (c) => {
101+
c.state.count += 1;
102+
c.broadcast("newCount", c.state.count);
103+
return c.state.count;
104+
},
105+
getCount: (c) => {
106+
return c.state.count;
107+
}
108+
}
109+
});
110+
111+
export const app = setup({
112+
actors: { counter }
113+
});
114+
115+
export type App = typeof app;
116+
```
117+
</CodeGroup>
118+
119+
## Testing Events
120+
121+
For actors that emit events, you can verify events are correctly triggered by subscribing to them:
122+
123+
<CodeGroup>
124+
```ts tests/chat-room.test.ts
125+
import { test, expect, vi } from "vitest";
126+
import { setupTest } from "actor-core/test";
127+
import { app } from "../src/index";
128+
129+
test("actor should emit events", async () => {
130+
const { client } = await setupTest(app);
131+
const chatRoom = await client.chatRoom.get();
132+
133+
// Set up event handler with a mock function
134+
const mockHandler = vi.fn();
135+
chatRoom.on("newMessage", mockHandler);
136+
137+
// Trigger the event
138+
await chatRoom.sendMessage("testUser", "Hello world");
139+
140+
// Wait for the event to be emitted
141+
await vi.waitFor(() => {
142+
expect(mockHandler).toHaveBeenCalledWith("testUser", "Hello world");
143+
});
144+
});
145+
```
146+
147+
```ts src/index.ts
148+
import { actor, setup } from "actor-core";
149+
150+
export const chatRoom = actor({
151+
state: {
152+
messages: []
153+
},
154+
actions: {
155+
sendMessage: (c, username: string, message: string) => {
156+
c.state.messages.push({ username, message });
157+
c.broadcast("newMessage", username, message);
158+
},
159+
getHistory: (c) => {
160+
return c.state.messages;
161+
},
162+
},
163+
});
164+
165+
// Create and export the app
166+
export const app = setup({
167+
actors: { chatRoom }
168+
});
169+
170+
// Export type for client type checking
171+
export type App = typeof app;
172+
```
173+
</CodeGroup>
174+
175+
## Testing Schedules
176+
177+
ActorCore's schedule functionality can be tested using Vitest's time manipulation utilities:
178+
179+
<CodeGroup>
180+
```ts tests/scheduler.test.ts
181+
import { test, expect, vi } from "vitest";
182+
import { setupTest } from "actor-core/test";
183+
import { app } from "../src/index";
184+
185+
test("scheduled tasks should execute", async () => {
186+
// setupTest automatically configures vi.useFakeTimers()
187+
const { client } = await setupTest(app);
188+
const scheduler = await client.scheduler.get();
189+
190+
// Set up a scheduled task
191+
await scheduler.scheduleTask("reminder", 60000); // 1 minute in the future
192+
193+
// Fast-forward time by 1 minute
194+
await vi.advanceTimersByTimeAsync(60000);
195+
196+
// Verify the scheduled task executed
197+
expect(await scheduler.getCompletedTasks()).toContain("reminder");
198+
});
199+
```
200+
201+
```ts src/index.ts
202+
import { actor, setup } from "actor-core";
203+
204+
const scheduler = actor({
205+
state: {
206+
tasks: [],
207+
completedTasks: []
208+
},
209+
actions: {
210+
scheduleTask: (c, taskName: string, delayMs: number) => {
211+
c.state.tasks.push(taskName);
212+
// Schedule "completeTask" to run after the specified delay
213+
c.schedule.after(delayMs, "completeTask", taskName);
214+
return { success: true };
215+
},
216+
completeTask: (c, taskName: string) => {
217+
// This action will be called by the scheduler when the time comes
218+
c.state.completedTasks.push(taskName);
219+
return { completed: taskName };
220+
},
221+
getCompletedTasks: (c) => {
222+
return c.state.completedTasks;
223+
}
224+
}
225+
});
226+
227+
export const app = setup({
228+
actors: { scheduler }
229+
});
230+
231+
export type App = typeof app;
232+
```
233+
</CodeGroup>
234+
235+
The `setupTest` function automatically calls `vi.useFakeTimers()`, allowing you to control time in your tests with functions like `vi.advanceTimersByTimeAsync()`. This makes it possible to test scheduled operations without waiting for real time to pass.
236+
237+
## Best Practices
238+
239+
1. **Isolate tests**: Each test should run independently, avoiding shared state.
240+
2. **Test edge cases**: Verify how your actor handles invalid inputs, concurrent operations, and error conditions.
241+
3. **Mock time**: Use Vitest's timer mocks for testing scheduled operations.
242+
4. **Use realistic data**: Test with data that resembles production scenarios.
243+
244+
ActorCore's testing framework automatically handles server setup and teardown, so you can focus on writing effective tests for your business logic.

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@
6969
"pages": [
7070
"concepts/connections",
7171
"concepts/metadata",
72-
"concepts/cors",
72+
"concepts/testing",
7373
"concepts/logging",
74+
"concepts/cors",
7475
"concepts/scaling",
7576
"concepts/external-sql",
7677
{

0 commit comments

Comments
 (0)