# 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 }); }); ```