Skip to content

Vitest

RedwoodSDK supports integration testing using Vitest and Cloudflare Workers Pool.

Since tests run in an isolated worker process (powered by vitest-pool-workers), they cannot directly access your running application’s state or database bindings in the same way a unit test might.

To bridge this gap, this guide uses a pattern where the test runner communicates with your worker via a special HTTP route (/_test).

  1. Test Side: Uses an vitestInvoke helper to send a POST request with the action name and arguments.
  2. Worker Side: A handleVitestRequest handler receives the request, executes the actual Server Action within the worker’s context (with full access to ctx, D1, KV, etc.), and returns the result.

You can interpret this as “RPC from Test Runner to Worker”.

You will need two things in your vitest.config.ts:

  1. Use defineWorkersConfig from @cloudflare/vitest-pool-workers/config.
  2. Point the pool to your built wrangler.json.
vitest.config.ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
export default defineWorkersConfig({
test: {
include: ["src/**/*.test.{ts,tsx}"],
poolOptions: {
workers: {
wrangler: {
// Use the built worker output so `rwsdk/worker` and RSCs resolve correctly.
configPath: "./dist/worker/wrangler.json",
},
},
},
},
});

Expose a /_test route in your src/worker.tsx to handle incoming test requests using rwsdk-community.

You can find a complete working example in our Vitest Playground.

src/worker.tsx
import { render, route } from "rwsdk/router";
import { defineApp } from "rwsdk/worker";
import { handleVitestRequest } from "rwsdk-community/worker";
import * as appActions from "./app/actions";
import * as testUtils from "./app/test-utils";
export default defineApp([
// ... other middleware
// 1. Expose the test bridge route
route("/_test", {
post: ({ request }) => handleVitestRequest(request, {
...appActions,
...testUtils // Optional: expose specific test utilities
}),
}),
// ... your application routeswe use
render(Document, [route("/", Home)]),
]);

Use the vitestInvoke helper from rwsdk-community/test to call your exposed actions.

src/tests/example.test.ts
import { expect, it, describe, beforeAll } from "vitest";
import { vitestInvoke } from "rwsdk-community/test";
describe("Integration Test", () => {
it("should create an item", async () => {
// 1. Call a server action via the bridge
const id = await vitestInvoke<number>("createItem", "Test Item");
// 2. Verify result
expect(id).toBeGreaterThan(0);
// 3. Verify side effects (e.g. ask DB for count)
const count = await vitestInvoke<number>("getItemCount");
expect(count).toBe(1);
});
});