cloudflare
references/do-storage/testing.md
.md 184 lines
Content
# DO Storage Testing
Testing Durable Objects with storage using `vitest-pool-workers`.
## Setup
**vitest.config.ts:**
```typescript
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
export default defineWorkersConfig({
test: {
poolOptions: {
workers: { wrangler: { configPath: "./wrangler.toml" } }
}
}
});
```
**package.json:** Add `@cloudflare/vitest-pool-workers` and `vitest` to devDependencies
## Basic Testing
```typescript
import { env, runInDurableObject } from "cloudflare:test";
import { describe, it, expect } from "vitest";
describe("Counter DO", () => {
it("increments counter", async () => {
const id = env.COUNTER.idFromName("test");
const result = await runInDurableObject(env.COUNTER, id, async (instance, state) => {
const val1 = await instance.increment();
const val2 = await instance.increment();
return { val1, val2 };
});
expect(result.val1).toBe(1);
expect(result.val2).toBe(2);
});
});
```
## Testing SQL Storage
```typescript
it("creates and queries users", async () => {
const id = env.USER_MANAGER.idFromName("test");
await runInDurableObject(env.USER_MANAGER, id, async (instance, state) => {
await instance.createUser("alice@example.com", "Alice");
const user = await instance.getUser("alice@example.com");
expect(user).toEqual({ email: "alice@example.com", name: "Alice" });
});
});
it("handles schema migrations", async () => {
const id = env.USER_MANAGER.idFromName("migration-test");
await runInDurableObject(env.USER_MANAGER, id, async (instance, state) => {
const version = state.storage.sql.exec(
"SELECT value FROM _meta WHERE key = 'schema_version'"
).one()?.value;
expect(version).toBe("1");
});
});
```
## Testing Alarms
```typescript
import { runDurableObjectAlarm } from "cloudflare:test";
it("processes batch on alarm", async () => {
const id = env.BATCH_PROCESSOR.idFromName("test");
// Add items
await runInDurableObject(env.BATCH_PROCESSOR, id, async (instance) => {
await instance.addItem("item1");
await instance.addItem("item2");
});
// Trigger alarm
await runDurableObjectAlarm(env.BATCH_PROCESSOR, id);
// Verify processed
await runInDurableObject(env.BATCH_PROCESSOR, id, async (instance, state) => {
const count = state.storage.sql.exec(
"SELECT COUNT(*) as count FROM processed_items"
).one().count;
expect(count).toBe(2);
});
});
```
## Testing Concurrency
```typescript
it("handles concurrent increments safely", async () => {
const id = env.COUNTER.idFromName("concurrent-test");
// Parallel increments
const results = await Promise.all([
runInDurableObject(env.COUNTER, id, (i) => i.increment()),
runInDurableObject(env.COUNTER, id, (i) => i.increment()),
runInDurableObject(env.COUNTER, id, (i) => i.increment())
]);
// All should get unique values
expect(new Set(results).size).toBe(3);
expect(Math.max(...results)).toBe(3);
});
```
## Test Isolation
```typescript
// Per-test unique IDs
let testId: string;
beforeEach(() => { testId = crypto.randomUUID(); });
it("isolated test", async () => {
const id = env.MY_DO.idFromName(testId);
// Uses unique DO instance
});
// Cleanup pattern
it("with cleanup", async () => {
const id = env.MY_DO.idFromName("cleanup-test");
try {
await runInDurableObject(env.MY_DO, id, async (instance) => {});
} finally {
await runInDurableObject(env.MY_DO, id, async (instance, state) => {
await state.storage.deleteAll();
});
}
});
```
## Testing PITR
```typescript
it("restores from bookmark", async () => {
const id = env.MY_DO.idFromName("pitr-test");
// Create checkpoint
const bookmark = await runInDurableObject(env.MY_DO, id, async (instance, state) => {
await state.storage.put("value", 1);
return await state.storage.getCurrentBookmark();
});
// Modify and restore
await runInDurableObject(env.MY_DO, id, async (instance, state) => {
await state.storage.put("value", 2);
await state.storage.onNextSessionRestoreBookmark(bookmark);
state.abort();
});
// Verify restored
await runInDurableObject(env.MY_DO, id, async (instance, state) => {
const value = await state.storage.get("value");
expect(value).toBe(1);
});
});
```
## Testing Transactions
```typescript
it("rolls back on error", async () => {
const id = env.BANK.idFromName("transaction-test");
await runInDurableObject(env.BANK, id, async (instance, state) => {
await state.storage.put("balance", 100);
await expect(
state.storage.transaction(async () => {
await state.storage.put("balance", 50);
throw new Error("Cancel");
})
).rejects.toThrow("Cancel");
const balance = await state.storage.get("balance");
expect(balance).toBe(100); // Rolled back
});
});
```