Test, migrate, and operate
Test object boundaries and lifecycle
Use the Workers Vitest pool to call typed stubs, inspect isolated storage, trigger alarms, and prove sharding.
Durable Object tests belong inside the Workers runtime, with real bindings. Mocking env.PROJECT_ROOM tests your mock. Cloudflare’s Vitest integration runs your tests inside workerd, so the env in a test is the same env your Worker sees.
Call the typed stub the way the Worker would:
import { env } from 'cloudflare:workers'
import { it, expect } from 'vitest'
it('isolates rooms', async () => {
const standup = env.PROJECT_ROOM.getByName('standup')
const design = env.PROJECT_ROOM.getByName('design')
await standup.addMessage('flavio', 'shipping today')
expect(await standup.listMessages()).toHaveLength(1)
expect(await design.listMessages()).toHaveLength(0)
})
Each test starts with isolated storage. A message written in one test never leaks into the next. That’s the default with the integration, and it’s what makes the second assertion trustworthy.
Look inside when the stub isn’t enough
Sometimes you need to see storage directly, or fire an alarm without waiting an hour. The cloudflare:test helpers do that:
import { runInDurableObject, runDurableObjectAlarm } from 'cloudflare:test'
await runInDurableObject(standup, async (instance, state) => {
const row = state.storage.sql.exec('select count(*) as n from messages').one()
expect(row.n).toBe(1)
})
const ran = await runDurableObjectAlarm(standup)
expect(ran).toBe(true)
runDurableObjectAlarm returns false when no alarm is scheduled. That’s a useful assertion on its own: after the handler runs the last task, the object should have nothing armed.
What to cover
Test behavior, not only HTTP status codes:
same room: writes are observed in order
different room: state is isolated
restart: durable state remains
duplicate request: effect is not duplicated
Add an alarm or WebSocket case only after the storage boundary passes. A local single-request test cannot prove coordination. Run concurrent clients and include one failure between receiving a request and completing its external side effect.
Write one direct RPC test and one full Worker request test for the same room action. The RPC test tells you the object logic is right. The request test, sent through SELF.fetch, tells you the routing and the authorization in front of the object are right. A green RPC suite with a broken Worker is a common way to ship a 403 on every room.
One failure to watch for: a test passes in wrangler dev because one request runs at a time. Then two users join a room and the invariant breaks. Concurrency bugs need concurrent tests. Promise.all two calls in the test and check the result.
Lesson completed