Skip to content

Commit f32493f

Browse files
committed
chore: update tests to be able to ran concurrently (#882)
1 parent 9ef010a commit f32493f

File tree

20 files changed

+180
-115
lines changed

20 files changed

+180
-115
lines changed

docs/concepts/testing.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import { test, expect } from "vitest";
2727
import { setupTest } from "actor-core/test";
2828
import { app } from "../src/index";
2929

30-
test("my actor test", async () => {
31-
const { client } = await setupTest(app);
30+
test("my actor test", async (test) => {
31+
const { client } = await setupTest(test, app);
3232

3333
// Now you can interact with your actor through the client
3434
const myActor = await client.myActor.get();
@@ -76,8 +76,8 @@ import { test, expect } from "vitest";
7676
import { setupTest } from "actor-core/test";
7777
import { app } from "../src/index";
7878

79-
test("actor should persist state", async () => {
80-
const { client } = await setupTest(app);
79+
test("actor should persist state", async (test) => {
80+
const { client } = await setupTest(test, app);
8181
const counter = await client.counter.get();
8282

8383
// Initial state
@@ -126,8 +126,8 @@ import { test, expect, vi } from "vitest";
126126
import { setupTest } from "actor-core/test";
127127
import { app } from "../src/index";
128128

129-
test("actor should emit events", async () => {
130-
const { client } = await setupTest(app);
129+
test("actor should emit events", async (test) => {
130+
const { client } = await setupTest(test, app);
131131
const chatRoom = await client.chatRoom.get();
132132

133133
// Set up event handler with a mock function
@@ -182,9 +182,9 @@ import { test, expect, vi } from "vitest";
182182
import { setupTest } from "actor-core/test";
183183
import { app } from "../src/index";
184184

185-
test("scheduled tasks should execute", async () => {
185+
test("scheduled tasks should execute", async (test) => {
186186
// setupTest automatically configures vi.useFakeTimers()
187-
const { client } = await setupTest(app);
187+
const { client } = await setupTest(test, app);
188188
const scheduler = await client.scheduler.get();
189189

190190
// Set up a scheduled task

examples/chat-room/tests/chat-room.test.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,59 @@ import { test, expect } from "vitest";
22
import { setupTest } from "actor-core/test";
33
import { app } from "../actors/app";
44

5-
test("chat room should handle messages", async () => {
6-
const { client } = await setupTest(app);
7-
5+
test("chat room should handle messages", async (test) => {
6+
const { client } = await setupTest(test, app);
7+
88
// Connect to chat room
99
const chatRoom = await client.chatRoom.get();
10-
10+
1111
// Initial history should be empty
1212
const initialMessages = await chatRoom.getHistory();
1313
expect(initialMessages).toEqual([]);
14-
14+
1515
// Test event emission
1616
let receivedUsername = "";
1717
let receivedMessage = "";
1818
chatRoom.on("newMessage", (username: string, message: string) => {
1919
receivedUsername = username;
2020
receivedMessage = message;
2121
});
22-
22+
2323
// Send a message
2424
const testUser = "william";
2525
const testMessage = "All the world's a stage.";
2626
await chatRoom.sendMessage(testUser, testMessage);
27-
27+
2828
// Verify event was emitted with correct data
2929
expect(receivedUsername).toBe(testUser);
3030
expect(receivedMessage).toBe(testMessage);
31-
31+
3232
// Verify message was stored in history
3333
const updatedMessages = await chatRoom.getHistory();
3434
expect(updatedMessages).toEqual([
35-
{ username: testUser, message: testMessage }
35+
{ username: testUser, message: testMessage },
3636
]);
37-
37+
3838
// Send multiple messages and verify
3939
const users = ["romeo", "juliet", "othello"];
40-
const messages = ["Wherefore art thou?", "Here I am!", "The green-eyed monster."];
41-
40+
const messages = [
41+
"Wherefore art thou?",
42+
"Here I am!",
43+
"The green-eyed monster.",
44+
];
45+
4246
for (let i = 0; i < users.length; i++) {
4347
await chatRoom.sendMessage(users[i], messages[i]);
44-
48+
4549
// Verify event emission
4650
expect(receivedUsername).toBe(users[i]);
4751
expect(receivedMessage).toBe(messages[i]);
4852
}
49-
53+
5054
// Verify all messages are in history in correct order
5155
const finalHistory = await chatRoom.getHistory();
5256
expect(finalHistory).toEqual([
5357
{ username: testUser, message: testMessage },
54-
...users.map((username, i) => ({ username, message: messages[i] }))
58+
...users.map((username, i) => ({ username, message: messages[i] })),
5559
]);
5660
});

examples/counter/tests/counter.test.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,34 @@ import { test, expect } from "vitest";
22
import { setupTest } from "actor-core/test";
33
import { app } from "../actors/app";
44

5-
test("it should count", async () => {
6-
const { client } = await setupTest(app);
5+
test("it should count", async (test) => {
6+
const { client } = await setupTest(test, app);
77
const counter = await client.counter.get();
8-
8+
99
// Test initial count
1010
expect(await counter.getCount()).toBe(0);
11-
11+
1212
// Test event emission
1313
let eventCount = -1;
1414
counter.on("newCount", (count: number) => {
1515
eventCount = count;
1616
});
17-
17+
1818
// Test increment
1919
const incrementAmount = 5;
2020
const result = await counter.increment(incrementAmount);
2121
expect(result).toBe(incrementAmount);
22-
22+
2323
// Verify event was emitted with correct count
2424
expect(eventCount).toBe(incrementAmount);
25-
25+
2626
// Test multiple increments
2727
for (let i = 1; i <= 3; i++) {
2828
const newCount = await counter.increment(incrementAmount);
2929
expect(newCount).toBe(incrementAmount * (i + 1));
3030
expect(eventCount).toBe(incrementAmount * (i + 1));
3131
}
32-
32+
3333
// Verify final count
3434
expect(await counter.getCount()).toBe(incrementAmount * 4);
3535
});
36-

packages/actor-core/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@
172172
"@types/node": "^22.13.1",
173173
"@types/ws": "^8",
174174
"eventsource": "^3.0.5",
175-
"get-port": "^7.1.0",
176175
"tsup": "^8.4.0",
177176
"typescript": "^5.7.3",
178177
"vitest": "^3.1.1",

packages/actor-core/src/test/mod.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import {
1111
TestActorDriver,
1212
} from "./driver/mod";
1313
import { type InputConfig, ConfigSchema } from "./config";
14-
import { onTestFinished, vi } from "vitest";
15-
import getPort from "get-port";
14+
import { type TestContext, vi } from "vitest";
1615
import { type Client, createClient } from "@/client/mod";
16+
import { createServer } from "node:net";
1717

1818
function createRouter(
1919
app: ActorCoreApp<any>,
@@ -93,7 +93,9 @@ export interface SetupTestResult<A extends ActorCoreApp<any>> {
9393
};
9494
}
9595

96+
// Must use `TestContext` since global hooks do not work when running concurrently
9697
export async function setupTest<A extends ActorCoreApp<any>>(
98+
c: TestContext,
9799
app: A,
98100
): Promise<SetupTestResult<A>> {
99101
vi.useFakeTimers();
@@ -109,13 +111,13 @@ export async function setupTest<A extends ActorCoreApp<any>>(
109111
// Start server with a random port
110112
const port = await getPort();
111113
const server = serve(app, { port });
112-
onTestFinished(
114+
c.onTestFinished(
113115
async () => await new Promise((resolve) => server.close(() => resolve())),
114116
);
115117

116118
// Create client
117119
const client = createClient<A>(`http://127.0.0.1:${port}`);
118-
onTestFinished(async () => await client.dispose());
120+
c.onTestFinished(async () => await client.dispose());
119121

120122
return {
121123
client,
@@ -126,3 +128,53 @@ export async function setupTest<A extends ActorCoreApp<any>>(
126128
},
127129
};
128130
}
131+
132+
export async function getPort(): Promise<number> {
133+
// Pick random port between 10000 and 65535 (avoiding well-known and registered ports)
134+
const MIN_PORT = 10000;
135+
const MAX_PORT = 65535;
136+
const getRandomPort = () =>
137+
Math.floor(Math.random() * (MAX_PORT - MIN_PORT + 1)) + MIN_PORT;
138+
139+
let port = getRandomPort();
140+
let maxAttempts = 10;
141+
142+
while (maxAttempts > 0) {
143+
try {
144+
// Try to create a server on the port to check if it's available
145+
const server = await new Promise<any>((resolve, reject) => {
146+
const server = createServer();
147+
148+
server.once("error", (err: Error & { code?: string }) => {
149+
if (err.code === "EADDRINUSE") {
150+
reject(new Error(`Port ${port} is in use`));
151+
} else {
152+
reject(err);
153+
}
154+
});
155+
156+
server.once("listening", () => {
157+
resolve(server);
158+
});
159+
160+
server.listen(port);
161+
});
162+
163+
// Close the server since we're just checking availability
164+
await new Promise<void>((resolve) => {
165+
server.close(() => resolve());
166+
});
167+
168+
return port;
169+
} catch (err) {
170+
// If port is in use, try a different one
171+
maxAttempts--;
172+
if (maxAttempts <= 0) {
173+
break;
174+
}
175+
port = getRandomPort();
176+
}
177+
}
178+
179+
throw new Error("Could not find an available port after multiple attempts");
180+
}

packages/actor-core/tests/basic.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { actor, setup } from "@/mod";
22
import { test } from "vitest";
33
import { setupTest } from "@/test/mod";
44

5-
test("basic actor setup", async () => {
5+
test("basic actor setup", async (c) => {
66
const counter = actor({
77
state: { count: 0 },
88
actions: {
@@ -18,9 +18,8 @@ test("basic actor setup", async () => {
1818
actors: { counter },
1919
});
2020

21-
const { client } = await setupTest<typeof app>(app);
21+
const { client } = await setupTest<typeof app>(c, app);
2222

2323
const counterInstance = await client.counter.get();
2424
await counterInstance.increment(1);
2525
});
26-

packages/actor-core/tests/vars.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { setupTest } from "@/test/mod";
44

55
describe("Actor Vars", () => {
66
describe("Static vars", () => {
7-
test("should provide access to static vars", async () => {
7+
test("should provide access to static vars", async (c) => {
88
// Define actor with static vars
99
const varActor = actor({
1010
state: { value: 0 },
@@ -24,7 +24,7 @@ describe("Actor Vars", () => {
2424
actors: { varActor },
2525
});
2626

27-
const { client } = await setupTest<typeof app>(app);
27+
const { client } = await setupTest<typeof app>(c, app);
2828
const instance = await client.varActor.get();
2929

3030
// Test accessing vars
@@ -38,7 +38,7 @@ describe("Actor Vars", () => {
3838
});
3939

4040
describe("Deep cloning of static vars", () => {
41-
test("should deep clone static vars between actor instances", async () => {
41+
test("should deep clone static vars between actor instances", async (c) => {
4242
// Define actor with nested object in vars
4343
const nestedVarActor = actor({
4444
state: { value: 0 },
@@ -69,7 +69,7 @@ describe("Actor Vars", () => {
6969
actors: { nestedVarActor },
7070
});
7171

72-
const { client } = await setupTest<typeof app>(app);
72+
const { client } = await setupTest<typeof app>(c, app);
7373

7474
// Create two separate instances
7575
const instance1 = await client.nestedVarActor.get({
@@ -94,7 +94,7 @@ describe("Actor Vars", () => {
9494
});
9595

9696
describe("createVars", () => {
97-
test("should support dynamic vars creation", async () => {
97+
test("should support dynamic vars creation", async (c) => {
9898
// Define actor with createVars function
9999
const dynamicVarActor = actor({
100100
state: { value: 0 },
@@ -116,7 +116,7 @@ describe("Actor Vars", () => {
116116
actors: { dynamicVarActor },
117117
});
118118

119-
const { client } = await setupTest<typeof app>(app);
119+
const { client } = await setupTest<typeof app>(c, app);
120120

121121
// Create an instance
122122
const instance = await client.dynamicVarActor.get();
@@ -130,7 +130,7 @@ describe("Actor Vars", () => {
130130
expect(vars.computed).toMatch(/^Actor-\d+$/);
131131
});
132132

133-
test("should create different vars for different instances", async () => {
133+
test("should create different vars for different instances", async (c) => {
134134
// Define actor with createVars function that generates unique values
135135
const uniqueVarActor = actor({
136136
state: { value: 0 },
@@ -151,7 +151,7 @@ describe("Actor Vars", () => {
151151
actors: { uniqueVarActor },
152152
});
153153

154-
const { client } = await setupTest<typeof app>(app);
154+
const { client } = await setupTest<typeof app>(c, app);
155155

156156
// Create two separate instances
157157
const instance1 = await client.uniqueVarActor.get({
@@ -171,7 +171,7 @@ describe("Actor Vars", () => {
171171
});
172172

173173
describe("Driver Context", () => {
174-
test("should provide access to driver context", async () => {
174+
test("should provide access to driver context", async (c) => {
175175
// Reset timers to avoid test timeouts
176176
vi.useRealTimers();
177177

@@ -201,7 +201,7 @@ describe("Actor Vars", () => {
201201
});
202202

203203
// Set up the test
204-
const { client } = await setupTest<typeof app>(app);
204+
const { client } = await setupTest<typeof app>(c, app);
205205

206206
// Create an instance
207207
const instance = await client.driverCtxActor.get();

packages/actor-core/vitest.config.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1+
import defaultConfig from "../../vitest.base.ts";
12
import { defineConfig } from "vitest/config";
23
import { resolve } from "path";
34

45
export default defineConfig({
5-
test: {
6-
globals: true,
7-
environment: "node",
8-
},
6+
...defaultConfig,
97
resolve: {
108
alias: {
119
"@": resolve(__dirname, "./src"),
1210
},
1311
},
1412
});
15-

0 commit comments

Comments
 (0)