diff --git a/src/content/docs/durable-objects/best-practices/rules-of-durable-objects.mdx b/src/content/docs/durable-objects/best-practices/rules-of-durable-objects.mdx index 50430798145..661bb1e16b2 100644 --- a/src/content/docs/durable-objects/best-practices/rules-of-durable-objects.mdx +++ b/src/content/docs/durable-objects/best-practices/rules-of-durable-objects.mdx @@ -1498,8 +1498,8 @@ Use `@cloudflare/vitest-pool-workers` for testing Durable Objects. The integrati ```ts +import { env } from "cloudflare:workers"; import { - env, runInDurableObject, runDurableObjectAlarm, } from "cloudflare:test"; @@ -1543,16 +1543,15 @@ describe("ChatRoom", () => { Configure Vitest in your `vitest.config.ts`: ```ts -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], }); ``` diff --git a/src/content/docs/durable-objects/examples/testing-with-durable-objects.mdx b/src/content/docs/durable-objects/examples/testing-with-durable-objects.mdx index 857312fb7de..30795e9c3ac 100644 --- a/src/content/docs/durable-objects/examples/testing-with-durable-objects.mdx +++ b/src/content/docs/durable-objects/examples/testing-with-durable-objects.mdx @@ -19,17 +19,17 @@ Install Vitest and the Workers Vitest integration as dev dependencies: ```sh -npm i -D vitest@~3.2.0 @cloudflare/vitest-pool-workers +npm i -D vitest@^4.1.0 @cloudflare/vitest-pool-workers ``` ```sh -pnpm add -D vitest@~3.2.0 @cloudflare/vitest-pool-workers +pnpm add -D vitest@^4.1.0 @cloudflare/vitest-pool-workers ``` ```sh -yarn add -D vitest@~3.2.0 @cloudflare/vitest-pool-workers +yarn add -D vitest@^4.1.0 @cloudflare/vitest-pool-workers ``` @@ -106,19 +106,18 @@ export default { ## Configure Vitest -Create a `vitest.config.ts` file that uses `defineWorkersConfig`: +Create a `vitest.config.ts` file that uses the `cloudflareTest()` plugin: ```ts title="vitest.config.ts" -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], }); ``` @@ -160,7 +159,7 @@ Create a `test/tsconfig.json` to configure TypeScript for your tests: Create an `env.d.ts` file to type the test environment: ```ts title="test/env.d.ts" -declare module "cloudflare:test" { +declare module "cloudflare:workers" { interface ProvidedEnv extends Env {} } ``` @@ -169,11 +168,11 @@ declare module "cloudflare:test" { ### Unit tests with direct Durable Object access -You can get a stub to a Durable Object directly from the `env` object provided by `cloudflare:test`: +You can get a stub to a Durable Object directly from the `env` object provided by `cloudflare:workers`: ```ts -import { env } from "cloudflare:test"; +import { env } from "cloudflare:workers"; import { describe, it, expect, beforeEach } from "vitest"; describe("Counter Durable Object", () => { @@ -237,18 +236,18 @@ describe("Counter Durable Object", () => { ``` -### Integration tests with SELF +### Integration tests with `exports` -Use the `SELF` fetcher to test your Worker's HTTP handler, which routes requests to Durable Objects: +Use `exports.default.fetch()` to test your Worker's HTTP handler, which routes requests to Durable Objects: ```ts -import { SELF } from "cloudflare:test"; +import { exports } from "cloudflare:workers"; import { describe, it, expect } from "vitest"; describe("Counter Worker integration", () => { it("should increment via HTTP POST", async () => { - const response = await SELF.fetch("http://example.com?id=http-test", { + const response = await exports.default.fetch("http://example.com?id=http-test", { method: "POST", }); @@ -259,22 +258,22 @@ describe("Counter Worker integration", () => { it("should get count via HTTP GET", async () => { // First increment the counter - await SELF.fetch("http://example.com?id=get-test", { method: "POST" }); - await SELF.fetch("http://example.com?id=get-test", { method: "POST" }); + await exports.default.fetch("http://example.com?id=get-test", { method: "POST" }); + await exports.default.fetch("http://example.com?id=get-test", { method: "POST" }); // Then get the count - const response = await SELF.fetch("http://example.com?id=get-test"); + const response = await exports.default.fetch("http://example.com?id=get-test"); const data = await response.json<{ count: number }>(); expect(data.count).toBe(2); }); it("should use different counters for different IDs", async () => { - await SELF.fetch("http://example.com?id=counter-a", { method: "POST" }); - await SELF.fetch("http://example.com?id=counter-a", { method: "POST" }); - await SELF.fetch("http://example.com?id=counter-b", { method: "POST" }); + await exports.default.fetch("http://example.com?id=counter-a", { method: "POST" }); + await exports.default.fetch("http://example.com?id=counter-a", { method: "POST" }); + await exports.default.fetch("http://example.com?id=counter-b", { method: "POST" }); - const responseA = await SELF.fetch("http://example.com?id=counter-a"); - const responseB = await SELF.fetch("http://example.com?id=counter-b"); + const responseA = await exports.default.fetch("http://example.com?id=counter-a"); + const responseB = await exports.default.fetch("http://example.com?id=counter-b"); const dataA = await responseA.json<{ count: number }>(); const dataB = await responseB.json<{ count: number }>(); @@ -292,8 +291,8 @@ Use `runInDurableObject()` to access instance properties and storage directly. T ```ts +import { env } from "cloudflare:workers"; import { - env, runInDurableObject, listDurableObjectIds, } from "cloudflare:test"; @@ -349,7 +348,8 @@ Each test automatically gets isolated storage. Durable Objects created in one te ```ts -import { env, listDurableObjectIds } from "cloudflare:test"; +import { env } from "cloudflare:workers"; +import { listDurableObjectIds } from "cloudflare:test"; import { describe, it, expect } from "vitest"; describe("Test isolation", () => { @@ -382,7 +382,8 @@ SQLite-backed Durable Objects work seamlessly in tests. The SQL API is available ```ts -import { env, runInDurableObject } from "cloudflare:test"; +import { env } from "cloudflare:workers"; +import { runInDurableObject } from "cloudflare:test"; import { describe, it, expect } from "vitest"; describe("SQLite in Durable Objects", () => { @@ -421,8 +422,8 @@ Use `runDurableObjectAlarm()` to immediately trigger a scheduled alarm without w ```ts +import { env } from "cloudflare:workers"; import { - env, runInDurableObject, runDurableObjectAlarm, } from "cloudflare:test"; diff --git a/src/content/docs/workers/best-practices/workers-best-practices.mdx b/src/content/docs/workers/best-practices/workers-best-practices.mdx index 78449a1148f..00c71010825 100644 --- a/src/content/docs/workers/best-practices/workers-best-practices.mdx +++ b/src/content/docs/workers/best-practices/workers-best-practices.mdx @@ -918,7 +918,7 @@ One known pitfall: the Vitest pool automatically injects `nodejs_compat`, so tes ```ts import { describe, it, expect } from "vitest"; -import { env } from "cloudflare:test"; +import { env } from "cloudflare:workers"; describe("KV operations", () => { it("should store and retrieve a value", async () => { diff --git a/src/content/docs/workers/testing/vitest-integration/configuration.mdx b/src/content/docs/workers/testing/vitest-integration/configuration.mdx index fd1430411b3..7211bd492d5 100644 --- a/src/content/docs/workers/testing/vitest-integration/configuration.mdx +++ b/src/content/docs/workers/testing/vitest-integration/configuration.mdx @@ -9,23 +9,22 @@ description: Vitest configuration specific to the Workers integration. import { Details } from "~/components"; -The Workers Vitest integration provides additional configuration on top of Vitest's usual options using the [`defineWorkersConfig()`](/workers/testing/vitest-integration/configuration/#defineworkersconfigoptions) API. +The Workers Vitest integration provides additional configuration on top of Vitest's usual options using the `cloudflareTest()` Vite plugin. An example configuration would be: ```ts -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { - configPath: "./wrangler.toml", - }, +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", }, - }, - }, + }), + ], }); ``` @@ -37,205 +36,92 @@ Custom Vitest `environment`s or `runner`s are not supported when using the Worke ## APIs -The following APIs are exported from the `@cloudflare/vitest-pool-workers/config` module. +The following APIs are exported from the `@cloudflare/vitest-pool-workers` package. -### `defineWorkersConfig(options)` +### `cloudflareTest(options)` -Ensures Vitest is configured to use the Workers integration with the correct module resolution settings, and provides type checking for [WorkersPoolOptions](#workerspooloptions). This should be used in place of the [`defineConfig()`](https://vitest.dev/config/file.html) function from Vitest. +A Vite plugin that configures Vitest to use the Workers integration with the correct module resolution settings, and provides type checking for [CloudflareTestOptions](#cloudflaretestoptions). Add this to the `plugins` array in your Vitest config alongside [`defineConfig()`](https://vitest.dev/config/file.html) from Vitest. -It also accepts a `Promise` of `options`, or an optionally-`async` function returning `options`. +It also accepts an optionally-`async` function returning `options`. ```ts -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - // Refer to type of WorkersPoolOptions... - }, - }, - }, +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + // Refer to CloudflareTestOptions... + }), + ], }); ``` -### `defineWorkersProject(options)` - -Use [`defineWorkersProject`](#defineworkersprojectoptions) with [Vitest Workspaces](https://vitest.dev/guide/workspace) to specify a different configuration for certain tests. It should be used in place of the [`defineProject()`](https://vitest.dev/guide/workspace) function from Vitest. - -Similar to [`defineWorkersConfig()`](#defineworkersconfigoptions), this ensures Vitest is configured to use the Workers integration with the correct module resolution settings, and provides type checking for [WorkersPoolOptions](#workerspooloptions). - -It also accepts a `Promise` of `options`, or an optionally-`async` function returning `options`. - -```ts -import { defineWorkspace, defineProject } from "vitest/config"; -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; - -const workspace = defineWorkspace([ - defineWorkersProject({ - test: { - name: "Workers", - include: ["**/*.worker.test.ts"], - poolOptions: { - workers: { - // Refer to type of WorkersPoolOptions... - }, - }, - }, - }), - - // ... -]); - -export default workspace; -``` - ### `buildPagesASSETSBinding(assetsPath)` -Creates a Pages ASSETS binding that serves files insides the `assetsPath`. This is required if you uses `createPagesEventContext()` or `SELF` to test your **Pages Functions**. Refer to the [Pages recipe](/workers/testing/vitest-integration/recipes) for a full example. +Exported from `@cloudflare/vitest-pool-workers/config`. Creates a Pages ASSETS binding that serves files inside the `assetsPath`. This is required if you use `createPagesEventContext()` to test your **Pages Functions**. Refer to the [Pages recipe](/workers/testing/vitest-integration/recipes) for a full example. ```ts import path from "node:path"; -import { - buildPagesASSETSBinding, - defineWorkersProject, -} from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersProject(async () => { - const assetsPath = path.join(__dirname, "public"); - - return { - test: { - poolOptions: { - workers: { - miniflare: { - serviceBindings: { - ASSETS: await buildPagesASSETSBinding(assetsPath), - }, +import { buildPagesASSETSBinding } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest(async () => { + const assetsPath = path.join(__dirname, "public"); + + return { + miniflare: { + serviceBindings: { + ASSETS: await buildPagesASSETSBinding(assetsPath), }, }, - }, - }, - }; + }; + }), + ], }); ``` ### `readD1Migrations(migrationsPath)` -Reads all [D1 migrations](/d1/reference/migrations/) stored at `migrationsPath` and returns them ordered by migration number. Each migration will have its contents split into an array of individual SQL queries. Call the [`applyD1Migrations()`](/workers/testing/vitest-integration/test-apis/#d1) function inside a test or [setup file](https://vitest.dev/config/#setupfiles) to apply migrations. Refer to the [D1 recipe](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples/d1) for an example project using migrations. +Exported from `@cloudflare/vitest-pool-workers/config`. Reads all [D1 migrations](/d1/reference/migrations/) stored at `migrationsPath` and returns them ordered by migration number. Each migration will have its contents split into an array of individual SQL queries. Call the [`applyD1Migrations()`](/workers/testing/vitest-integration/test-apis/#d1) function inside a test or [setup file](https://vitest.dev/config/#setupfiles) to apply migrations. Refer to the [D1 recipe](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples/d1) for an example project using migrations. ```ts import path from "node:path"; -import { - defineWorkersProject, - readD1Migrations, -} from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersProject(async () => { - // Read all migrations in the `migrations` directory - const migrationsPath = path.join(__dirname, "migrations"); - const migrations = await readD1Migrations(migrationsPath); - - return { - test: { - setupFiles: ["./test/apply-migrations.ts"], - poolOptions: { - workers: { - miniflare: { - // Add a test-only binding for migrations, so we can apply them in a setup file - bindings: { TEST_MIGRATIONS: migrations }, - }, +import { readD1Migrations } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest(async () => { + const migrationsPath = path.join(__dirname, "migrations"); + const migrations = await readD1Migrations(migrationsPath); + + return { + miniflare: { + // Add a test-only binding for migrations, so we can apply them in a setup file + bindings: { TEST_MIGRATIONS: migrations }, }, - }, - }, - }; + }; + }), + ], + test: { + setupFiles: ["./test/apply-migrations.ts"], + }, }); ``` -## `WorkersPoolOptions` +## `CloudflareTestOptions` -- `main`: string optional +Options passed directly to `cloudflareTest()`. - - Entry point to Worker run in the same isolate/context as tests. This option is required to use `import { SELF } from "cloudflare:test"` for integration tests, or Durable Objects without an explicit `scriptName` if classes are defined in the same Worker. This file goes through Vite transforms and can be TypeScript. Note that `import module from ""` inside tests gives exactly the same `module` instance as is used internally for the `SELF` and Durable Object bindings. If `wrangler.configPath` is defined and this option is not, it will be read from the `main` field in that configuration file. - -- `isolatedStorage`: boolean optional - - - Enables per-test isolated storage. If enabled, any writes to storage performed in a test will be undone at the end of the test. The test's storage environment is copied from the containing suite, meaning `beforeAll()` hooks can be used to seed data. If this option is disabled, all tests will share the same storage. `.concurrent` tests are not supported when isolated storage is enabled. Refer to [Isolation and concurrency](/workers/testing/vitest-integration/isolation-and-concurrency/) for more information on the isolation model. - - - Defaults to `true`. - -
- - ```ts - import { env } from "cloudflare:test"; - import { beforeAll, beforeEach, describe, test, expect } from "vitest"; - - // Get the current list stored in a KV namespace - async function get(): Promise { - return (await env.NAMESPACE.get("list", "json")) ?? []; - } - // Add an item to the end of the list - async function append(item: string) { - const value = await get(); - value.push(item); - await env.NAMESPACE.put("list", JSON.stringify(value)); - } - - beforeAll(() => append("all")); - beforeEach(() => append("each")); - - test("one", async () => { - // Each test gets its own storage environment copied from the parent - await append("one"); - expect(await get()).toStrictEqual(["all", "each", "one"]); - }); - // `append("each")` and `append("one")` undone - test("two", async () => { - await append("two"); - expect(await get()).toStrictEqual(["all", "each", "two"]); - }); - // `append("each")` and `append("two")` undone - - describe("describe", async () => { - beforeAll(() => append("describe all")); - beforeEach(() => append("describe each")); - - test("three", async () => { - await append("three"); - expect(await get()).toStrictEqual([ - // All `beforeAll()`s run before `beforeEach()`s - "all", - "describe all", - "each", - "describe each", - "three", - ]); - }); - // `append("each")`, `append("describe each")` and `append("three")` undone - test("four", async () => { - await append("four"); - expect(await get()).toStrictEqual([ - "all", - "describe all", - "each", - "describe each", - "four", - ]); - }); - // `append("each")`, `append("describe each")` and `append("four")` undone - }); - ``` - -
- -- `singleWorker`: boolean optional - - - Runs all tests in this project serially in the same Worker, using the same module cache. This can significantly speed up execution if you have lots of small test files. Refer to the [Isolation and concurrency](/workers/testing/vitest-integration/isolation-and-concurrency/) page for more information on the isolation model. - - - Defaults to `false`. +- `main`: string optional + - Entry point to Worker run in the same isolate/context as tests. This option is required to use Durable Objects without an explicit `scriptName` if classes are defined in the same Worker. This file goes through Vite transforms and can be TypeScript. Note that `import module from ""` inside tests gives exactly the same `module` instance as is used internally for `exports` and Durable Object bindings. If `wrangler.configPath` is defined and this option is not, it will be read from the `main` field in that configuration file. - `miniflare`: `SourcelessWorkerOptions & { workers?: WorkerOptions\[]; }` optional - - Use this to provide configuration information that is typically stored within the [Wrangler configuration file](/workers/wrangler/configuration/), such as [bindings](/workers/runtime-apis/bindings/), [compatibility dates](/workers/configuration/compatibility-dates/), and [compatibility flags](/workers/configuration/compatibility-flags/). The `WorkerOptions` interface is defined [here](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare#interface-workeroptions). Use the `main` option above to configure the entry point, instead of the Miniflare `script`, `scriptPath`, or `modules` options. - If your project makes use of multiple Workers, you can configure auxiliary Workers that run in the same `workerd` process as your tests and can be bound to. Auxiliary Workers are configured using the `workers` array, containing regular Miniflare [`WorkerOptions`](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare#interface-workeroptions) objects. Note that unlike the `main` Worker, auxiliary Workers: @@ -247,57 +133,55 @@ export default defineWorkersProject(async () => { - Are not affected by global mocks defined in your tests. - `wrangler`: `{ configPath?: string; environment?: string; }` optional - - Path to [Wrangler configuration file](/workers/wrangler/configuration/) to load `main`, [compatibility settings](/workers/configuration/compatibility-dates/) and [bindings](/workers/runtime-apis/bindings/) from. These options will be merged with the `miniflare` option above, with `miniflare` values taking precedence. For example, if your Wrangler configuration defined a [service binding](/workers/runtime-apis/bindings/service-bindings/) named `SERVICE` to a Worker named `service`, but you included `serviceBindings: { SERVICE(request) { return new Response("body"); } }` in the `miniflare` option, all requests to `SERVICE` in tests would return `body`. Note `configPath` accepts both `.toml` and `.json` files. - The environment option can be used to specify the [Wrangler environment](/workers/wrangler/environments/) to pick up bindings and variables from. -## `WorkersPoolOptionsContext` - -- `inject`: typeof import("vitest").inject - - - The same `inject()` function usually imported from the `vitest` module inside tests. This allows you to define `miniflare` configuration based on injected values from [`globalSetup`](https://vitest.dev/config/#globalsetup) scripts. Use this if you have a value in your configuration that is dynamically generated and only known at runtime of your tests. For example, a global setup script might start an upstream server on a random port. This port could be `provide()`d and then `inject()`ed in the configuration for an external service binding or [Hyperdrive](/hyperdrive/). Refer to the [Hyperdrive recipe](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples/hyperdrive) for an example project using this provide/inject approach. - -
- - ```ts - // env.d.ts - declare module "vitest" { - interface ProvidedContext { - port: number; - } - } - - // global-setup.ts - import type { GlobalSetupContext } from "vitest/node"; - export default function ({ provide }: GlobalSetupContext) { - // Runs inside Node.js, could start server here... - provide("port", 1337); - return () => { - /* ...then teardown here */ - }; - } - - // vitest.config.ts - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - globalSetup: ["./global-setup.ts"], - pool: "@cloudflare/vitest-pool-workers", - poolOptions: { - workers: ({ inject }) => ({ - miniflare: { - hyperdrives: { - DATABASE: `postgres://user:pass@example.com:${inject("port")}/db`, - }, - }, - }), - }, - }, - }); - ``` - -
+## Dynamic configuration with `inject` + +You can pass an `async` function to `cloudflareTest()` that receives an `inject` function. This allows you to define `miniflare` configuration based on injected values from [`globalSetup`](https://vitest.dev/config/#globalsetup) scripts. Use this if you have a value in your configuration that is dynamically generated and only known at runtime of your tests. For example, a global setup script might start an upstream server on a random port. This port could be `provide()`d and then `inject()`ed in the configuration for an external service binding or [Hyperdrive](/hyperdrive/). Refer to the [Hyperdrive recipe](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples/hyperdrive) for an example project using this provide/inject approach. + +
+ +```ts +// env.d.ts +declare module "vitest" { + interface ProvidedContext { + port: number; + } +} + +// global-setup.ts +import type { GlobalSetupContext } from "vitest/node"; +export default function ({ provide }: GlobalSetupContext) { + // Runs inside Node.js, could start server here... + provide("port", 1337); + return () => { + /* ...then teardown here */ + }; +} + +// vitest.config.ts +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest(({ inject }) => ({ + miniflare: { + hyperdrives: { + DATABASE: `postgres://user:pass@example.com:${inject("port")}/db`, + }, + }, + })), + ], + test: { + globalSetup: ["./global-setup.ts"], + }, +}); +``` + +
## `SourcelessWorkerOptions` diff --git a/src/content/docs/workers/testing/vitest-integration/debugging.mdx b/src/content/docs/workers/testing/vitest-integration/debugging.mdx index 6ef97d4553c..f41ccfd46d9 100644 --- a/src/content/docs/workers/testing/vitest-integration/debugging.mdx +++ b/src/content/docs/workers/testing/vitest-integration/debugging.mdx @@ -28,19 +28,20 @@ vitest --inspect=3456 --no-file-parallelism Alternatively, you can define it in your Vitest configuration file: ```ts -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersConfig({ - test: { - inspector: { - port: 3456, - }, - poolOptions: { - workers: { - // ... - }, - }, - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + // ... + }), + ], + test: { + inspector: { + port: 3456, + }, + }, }); ``` @@ -50,33 +51,36 @@ To setup VS Code for breakpoint debugging in your Worker tests, create a `.vscod ```json { - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Open inspector with Vitest", - "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", - "console": "integratedTerminal", - "args": ["--inspect=9229", "--no-file-parallelism"] - }, - { - "name": "Attach to Workers Runtime", - "type": "node", - "request": "attach", - "port": 9229, - "cwd": "/", - "resolveSourceMapLocations": null, - "attachExistingChildren": false, - "autoAttachChildProcesses": false, - } - ], - "compounds": [ - { - "name": "Debug Workers tests", - "configurations": ["Open inspector with Vitest", "Attach to Workers Runtime"], - "stopAll": true - } - ] + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Open inspector with Vitest", + "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", + "console": "integratedTerminal", + "args": ["--inspect=9229", "--no-file-parallelism"] + }, + { + "name": "Attach to Workers Runtime", + "type": "node", + "request": "attach", + "port": 9229, + "cwd": "/", + "resolveSourceMapLocations": null, + "attachExistingChildren": false, + "autoAttachChildProcesses": false + } + ], + "compounds": [ + { + "name": "Debug Workers tests", + "configurations": [ + "Open inspector with Vitest", + "Attach to Workers Runtime" + ], + "stopAll": true + } + ] } ``` diff --git a/src/content/docs/workers/testing/vitest-integration/index.mdx b/src/content/docs/workers/testing/vitest-integration/index.mdx index bed779b4045..94614336cc2 100644 --- a/src/content/docs/workers/testing/vitest-integration/index.mdx +++ b/src/content/docs/workers/testing/vitest-integration/index.mdx @@ -13,10 +13,9 @@ The Workers Vitest integration: - Supports both **unit tests** and **integration tests**. - Provides direct access to Workers runtime APIs and bindings. -- Implements isolated per-test storage. +- Implements isolated per-test-file storage. - Runs tests fully-locally using [Miniflare](https://miniflare.dev/). - Leverages Vitest's hot-module reloading for near instant reruns. -- Provides a declarative interface for mocking outbound requests. - Supports projects with multiple Workers. diff --git a/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx b/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx index bbf6bf08123..b9152a67412 100644 --- a/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx +++ b/src/content/docs/workers/testing/vitest-integration/isolation-and-concurrency.mdx @@ -23,35 +23,11 @@ When you run your tests with the Workers Vitest integration, Vitest will: 5. Run [`setupFiles`](https://vitest.dev/config/#setupfiles) and test files in `workerd` using the appropriate Workers. 6. Watch for changes and re-run test files using the same Workers if the configuration has not changed. -## Isolation and concurrency models +## Isolation model -The [`isolatedStorage` and `singleWorker`](/workers/testing/vitest-integration/configuration/#workerspooloptions) configuration options both control isolation and concurrency. The Workers Vitest integration tries to minimise the number of `workerd` processes it starts, reusing Workers and their module caches between test runs where possible. The current implementation of isolated storage requires each `workerd` process to run one test file at a time, and does not support `.concurrent` tests. A copy of all auxiliary `workers` exists in each `workerd` process. +Storage isolation is per test file. Each test file gets its own storage environment, and any writes to storage during a test file are not visible to other test files. The Workers Vitest integration reuses Workers and their module caches between test runs where possible. A copy of all auxiliary `workers` exists in each `workerd` process. -By default, the `isolatedStorage` option is enabled. We recommend you enable the `singleWorker: true` option if you have lots of small test files. - -### `isolatedStorage: true, singleWorker: false` (Default) - -In this model, a `workerd` process is started for each test file. Test files are executed concurrently but `.concurrent` tests are not supported. Each test will read/write from an isolated storage environment, and bind to its own set of auxiliary `workers`. - -![Isolation Model: Isolated Storage & No Single Worker](~/assets/images/workers/testing/vitest/isolation-model-3-isolated-storage-no-single-worker.svg) - -### `isolatedStorage: true, singleWorker: true` - -In this model, a single `workerd` process is started with a single Worker for all test files. Test files are executed in serial and `.concurrent` tests are not supported. Each test will read/write from an isolated storage environment, and bind to the same auxiliary `workers`. - -![Isolation Model: Isolated Storage & Single Worker](~/assets/images/workers/testing/vitest/isolation-model-4-isolated-storage-single-worker.svg) - -### `isolatedStorage: false, singleWorker: false` - -In this model, a single `workerd` process is started with a Worker for each test file. Tests files are executed concurrently and `.concurrent` tests are supported. Every test will read/write from the same shared storage, and bind to the same auxiliary `workers`. - -![Isolation Model: No Isolated Storage & No Single Worker](~/assets/images/workers/testing/vitest/isolation-model-1-no-isolated-storage-no-single-worker.svg) - -### `isolatedStorage: false, singleWorker: true` - -In this model, a single `workerd` process is started with a single Worker for all test files. Test files are executed in serial but `.concurrent` tests are supported. Every test will read/write from the same shared storage, and bind to the same auxiliary `workers`. - -![Isolation Model: No Isolated Storage & Single Worker](~/assets/images/workers/testing/vitest/isolation-model-2-no-isolated-storage-single-worker.svg) +By default, test files run concurrently. To make test files share the same storage (for example, for integration tests that depend on shared state), use the Vitest flags `--max-workers=1 --no-isolate`. ## Modules @@ -94,7 +70,7 @@ The test is a simple assertion that the Worker managed to use `process`. ```typescript it('responds with "test"', async () => { - const response = await SELF.fetch("https://example.com/"); + const response = await exports.default.fetch("https://example.com/"); expect(await response.text()).toMatchInlineSnapshot(`"test"`); }); ``` diff --git a/src/content/docs/workers/testing/vitest-integration/known-issues.mdx b/src/content/docs/workers/testing/vitest-integration/known-issues.mdx index c327fda75a5..df3ea817044 100644 --- a/src/content/docs/workers/testing/vitest-integration/known-issues.mdx +++ b/src/content/docs/workers/testing/vitest-integration/known-issues.mdx @@ -5,7 +5,6 @@ sidebar: order: 9 head: [] description: Explore the known issues associated with the Workers Vitest integration. - --- The Workers Vitest pool is currently in open beta. The following are issues Cloudflare is aware of and fixing: @@ -18,9 +17,9 @@ Native code coverage via [V8](https://v8.dev/blog/javascript-code-coverage) is n Vitest's [fake timers](https://vitest.dev/guide/mocking.html#timers) do not apply to KV, R2 and cache simulators. For example, you cannot expire a KV key by advancing fake time. -### Dynamic `import()` statements with `SELF` and Durable Objects +### Dynamic `import()` statements with `exports` and Durable Objects -Dynamic `import()` statements do not work inside `export default { ... }` handlers when writing integration tests with `SELF`, or inside Durable Object event handlers. You must import and call your handlers directly, or use static `import` statements in the global scope. +Dynamic `import()` statements do not work inside `export default { ... }` handlers when writing integration tests with `exports.default.fetch()`, or inside Durable Object event handlers. You must import and call your handlers directly, or use static `import` statements in the global scope. ### Durable Object alarms @@ -28,11 +27,11 @@ Durable Object alarms are not reset between test runs and do not respect isolate ### WebSockets -Using WebSockets with Durable Objects with the [`isolatedStorage`](/workers/testing/vitest-integration/isolation-and-concurrency) flag turned on is not supported. You must set `isolatedStorage: false` in your `vitest.config.ts` file. +Using WebSockets with Durable Objects is not supported with per-file storage isolation. To work around this, run your tests with shared storage using `--max-workers=1 --no-isolate`. -### Isolated storage +### Storage isolation -When the `isolatedStorage` flag is enabled (the default), the test runner will undo any writes to the storage at the end of the test as detailed in the [isolation and concurrency documentation](/workers/testing/vitest-integration/isolation-and-concurrency/). However, Cloudflare recommends that you consider the following actions to avoid any common issues: +Storage isolation is per test file. The test runner will undo any writes to storage at the end of each test file as detailed in the [isolation and concurrency documentation](/workers/testing/vitest-integration/isolation-and-concurrency/). Cloudflare recommends the following actions to avoid common issues: #### Await all storage operations @@ -41,8 +40,8 @@ Always `await` all `Promise`s that read or write to storage services. ```ts // Example: Seed data beforeAll(async () => { - await env.KV.put('message', 'test message'); - await env.R2.put('file', 'hello-world'); + await env.KV.put("message", "test message"); + await env.R2.put("file", "hello-world"); }); ``` @@ -59,19 +58,19 @@ using result = await stub.getCounter(); When making requests via `fetch` or `R2.get()`, consume the entire response body, even if you are not asserting its content. For example: ```ts -test('check if file exists', async () => { - await env.R2.put('file', 'hello-world'); - const response = await env.R2.get('file'); +test("check if file exists", async () => { + await env.R2.put("file", "hello-world"); + const response = await env.R2.get("file"); expect(response).not.toBe(null); // Consume the response body even if you are not asserting it - await response.text() + await response.text(); }); ``` ### Missing properties on `ctx.exports` -The `ctx.exports` property provides access to the exports of the main (`SELF`) Worker. The Workers Vitest integration attempts to automatically infer these exports by statically analyzing the Worker source code using esbuild. However, complex build setups, such as those using virtual modules or wildcard re-exports that esbuild cannot follow, may result in missing properties on the `ctx.exports` object. +The `ctx.exports` property provides access to the exports of the main Worker. The Workers Vitest integration attempts to automatically infer these exports by statically analyzing the Worker source code using esbuild. However, complex build setups, such as those using virtual modules or wildcard re-exports that esbuild cannot follow, may result in missing properties on the `ctx.exports` object. For example, consider a Worker that re-exports an entrypoint from a virtual module using a wildcard export: @@ -85,19 +84,18 @@ In this case, any exports from `@virtual-module` (such as `MyEntrypoint`) cannot To work around this, add the `additionalExports` option to your Vitest configuration: ```ts -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - additionalExports: { - MyEntrypoint: "WorkerEntrypoint", - }, +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + additionalExports: { + MyEntrypoint: "WorkerEntrypoint", }, - }, - }, + }), + ], }); ``` @@ -107,10 +105,16 @@ The `additionalExports` option is a map where keys are the export names and valu If you encounter module resolution issues such as: `Error: Cannot use require() to import an ES Module` or `Error: No such module`, you can bundle these dependencies using the [deps.optimizer](https://vitest.dev/config/#deps-optimizer) option: -```tsx -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ +```ts +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + // ... + }), + ], test: { deps: { optimizer: { @@ -120,11 +124,6 @@ export default defineWorkersConfig({ }, }, }, - poolOptions: { - workers: { - // ... - }, - }, }, }); ``` @@ -138,41 +137,42 @@ To work around this, you can create a wrapper that uses Vite's SSR module loader ```ts // File: global-setup-wrapper.ts -import { createServer } from "vite" +import { createServer } from "vite"; // Import the actual global setup file with the correct setup -const mod = await viteImport("./global-setup.ts") +const mod = await viteImport("./global-setup.ts"); export default mod.default; // Helper to import the file with default node setup async function viteImport(file: string) { - const server = await createServer({ - root: import.meta.dirname, - configFile: false, - server: { middlewareMode: true, hmr: false, watch: null, ws: false }, - optimizeDeps: { noDiscovery: true }, - clearScreen: false, - }); - const mod = await server.ssrLoadModule(file); - await server.close(); - return mod; + const server = await createServer({ + root: import.meta.dirname, + configFile: false, + server: { middlewareMode: true, hmr: false, watch: null, ws: false }, + optimizeDeps: { noDiscovery: true }, + clearScreen: false, + }); + const mod = await server.ssrLoadModule(file); + await server.close(); + return mod; } ``` ```ts // File: vitest.config.ts -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + // ... + }), + ], test: { // Replace the globalSetup with the wrapper file globalSetup: ["./global-setup-wrapper.ts"], - poolOptions: { - workers: { - // ... - }, - }, }, }); ``` diff --git a/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-miniflare-2.mdx b/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-miniflare-2.mdx index 22b4848d907..cbe7912a039 100644 --- a/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-miniflare-2.mdx +++ b/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-miniflare-2.mdx @@ -30,29 +30,32 @@ First, you will need to uninstall the old environment and install the new pool. ```sh npm uninstall vitest-environment-miniflare -npm install --save-dev --save-exact vitest@~3.0.0 +npm install --save-dev vitest@^4.1.0 npm install --save-dev @cloudflare/vitest-pool-workers ``` ## Update your Vitest configuration file -After installing the Workers Vitest configuration, update your Vitest configuration file to use the pool instead. Most Miniflare configuration previously specified `environmentOptions` can be moved to `poolOptions.workers.miniflare` instead. Refer to [Miniflare's `WorkerOptions` interface](https://github.com/cloudflare/workers-sdk/blob/main/packages/miniflare/README.md#interface-workeroptions) for supported options and the [Miniflare version 2 to 3 migration guide](/workers/testing/miniflare/migrations/from-v2/) for more information. If you relied on configuration stored in a Wrangler file, set `wrangler.configPath` too. +After installing the Workers Vitest integration, update your Vitest configuration file to use the `cloudflareTest()` Vite plugin instead. Most Miniflare configuration previously specified in `environmentOptions` can be moved to the `miniflare` option in `cloudflareTest()`. Refer to [Miniflare's `WorkerOptions` interface](https://github.com/cloudflare/workers-sdk/blob/main/packages/miniflare/README.md#interface-workeroptions) for supported options and the [Miniflare version 2 to 3 migration guide](/workers/testing/miniflare/migrations/from-v2/) for more information. If you relied on configuration stored in a Wrangler file, set `wrangler.configPath` too. ```diff -+ import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; ++ import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; ++ import { defineConfig } from "vitest/config"; - export default defineWorkersConfig({ - test: { +- export default defineWorkersConfig({ +- test: { - environment: "miniflare", - environmentOptions: { ... }, -+ poolOptions: { -+ workers: { -+ miniflare: { ... }, -+ wrangler: { configPath: "./wrangler.toml" }, -+ }, -+ }, - }, - }); +- }, +- }); ++ export default defineConfig({ ++ plugins: [ ++ cloudflareTest({ ++ miniflare: { ... }, ++ wrangler: { configPath: "./wrangler.jsonc" }, ++ }), ++ ], ++ }); ``` ## Update your TypeScript configuration file @@ -74,11 +77,11 @@ If you are using TypeScript, update your `tsconfig.json` to include the correct ## Access bindings -To access [bindings](/workers/runtime-apis/bindings/) in your tests, use the `env` helper from the `cloudflare:test` module. +To access [bindings](/workers/runtime-apis/bindings/) in your tests, use the `env` helper from the `cloudflare:workers` module. ```diff import { it } from "vitest"; -+ import { env } from "cloudflare:test"; ++ import { env } from "cloudflare:workers"; it("does something", () => { - const env = getMiniflareBindings(); @@ -86,21 +89,11 @@ To access [bindings](/workers/runtime-apis/bindings/) in your tests, use the `en }); ``` -If you are using TypeScript, add an ambient `.d.ts` declaration file defining a `ProvidedEnv` `interface` in the `cloudflare:test` module to control the type of `env`: +If you are using TypeScript, you need to define the type of `env` for your tests. Refer to [Define types](/workers/testing/vitest-integration/write-your-first-test/#define-types) for setup instructions. -```ts -declare module "cloudflare:test" { - interface ProvidedEnv { - NAMESPACE: KVNamespace; - } - // ...or if you have an existing `Env` type... - interface ProvidedEnv extends Env {} -} -``` +## Storage isolation -## Use isolated storage - -Isolated storage is now enabled by default. You no longer need to include `setupMiniflareIsolatedStorage()` in your tests. +Storage isolation is per test file by default. You no longer need to include `setupMiniflareIsolatedStorage()` in your tests. ```diff - const describe = setupMiniflareIsolatedStorage(); @@ -126,33 +119,15 @@ The `new ExecutionContext()` constructor and `getMiniflareWaitUntil()` function ## Mock outbound requests -The `getMiniflareFetchMock()` function has been replaced with the new `fetchMock` helper from the `cloudflare:test` module. `fetchMock` has the same type as the return type of `getMiniflareFetchMock()`. There are a couple of differences between `fetchMock` and the previous return value of `getMiniflareFetchMock()`: - -- `fetchMock` is deactivated by default, whereas previously it would start activated. This deactivation prevents unnecessary buffering of request bodies if you are not using `fetchMock`. You will need to call `fetchMock.activate()` before calling `fetch()` to enable it. -- `fetchMock` is reset at the start of each test run, whereas previously, interceptors added in previous runs would apply to the current one. This ensures test runs are not affected by previous runs. - -```diff - import { beforeAll, afterAll } from "vitest"; -+ import { fetchMock } from "cloudflare:test"; - -- const fetchMock = getMiniflareFetchMock(); - beforeAll(() => { -+ fetchMock.activate(); - fetchMock.disableNetConnect(); - fetchMock - .get("https://example.com") - .intercept({ path: "/" }) - .reply(200, "data"); - }); - afterAll(() => fetchMock.assertNoPendingInterceptors()); -``` +The `getMiniflareFetchMock()` function is no longer available. To mock outbound `fetch()` requests, mock `globalThis.fetch` directly or use ecosystem libraries such as [MSW](https://mswjs.io/). Refer to the [request mocking example](https://github.com/cloudflare/workers-sdk/blob/main/fixtures/vitest-pool-workers-examples/request-mocking/test/imperative.test.ts) for a complete example. ## Use Durable Object helpers The `getMiniflareDurableObjectStorage()`, `getMiniflareDurableObjectState()`, `getMiniflareDurableObjectInstance()`, and `runWithMiniflareDurableObjectGates()` functions have all been replaced with a single `runInDurableObject()` function from the `cloudflare:test` module. The `runInDurableObject()` function accepts a `DurableObjectStub` with a callback accepting the Durable Object and corresponding `DurableObjectState` as arguments. Consolidating these functions into a single function simplifies the API surface, and ensures instances are accessed with the correct request context and [gating behavior](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three/). Refer to the [Test APIs page](/workers/testing/vitest-integration/test-apis/) for more details. ```diff -+ import { env, runInDurableObject } from "cloudflare:test"; ++ import { env } from "cloudflare:workers"; ++ import { runInDurableObject } from "cloudflare:test"; it("does something", async () => { - const env = getMiniflareBindings(); @@ -184,7 +159,8 @@ The `getMiniflareDurableObjectStorage()`, `getMiniflareDurableObjectState()`, `g The `flushMiniflareDurableObjectAlarms()` function has been replaced with the `runDurableObjectAlarm()` function from the `cloudflare:test` module. The `runDurableObjectAlarm()` function accepts a single `DurableObjectStub` and returns a `Promise` that resolves to `true` if an alarm was scheduled and the `alarm()` handler was executed, or `false` otherwise. To "flush" multiple instances' alarms, call `runDurableObjectAlarm()` in a loop. ```diff -+ import { env, runDurableObjectAlarm } from "cloudflare:test"; ++ import { env } from "cloudflare:workers"; ++ import { runDurableObjectAlarm } from "cloudflare:test"; it("does something", async () => { - const env = getMiniflareBindings(); @@ -195,10 +171,11 @@ The `flushMiniflareDurableObjectAlarms()` function has been replaced with the `r }); ``` -Finally, the `getMiniflareDurableObjectIds()` function has been replaced with the `listDurableObjectIds()` function from the `cloudflare:test` module. The `listDurableObjectIds()` function now accepts a `DurableObjectNamespace` instance instead of a namespace `string` to provide stricter typing. Note the `listDurableObjectIds()` function now respects isolated storage. If enabled, IDs of objects created in other tests will not be returned. +Finally, the `getMiniflareDurableObjectIds()` function has been replaced with the `listDurableObjectIds()` function from the `cloudflare:test` module. The `listDurableObjectIds()` function now accepts a `DurableObjectNamespace` instance instead of a namespace `string` to provide stricter typing. Note the `listDurableObjectIds()` function respects storage isolation. IDs of objects created in other test files will not be returned. ```diff -+ import { env, listDurableObjectIds } from "cloudflare:test"; ++ import { env } from "cloudflare:workers"; ++ import { listDurableObjectIds } from "cloudflare:test"; it("does something", async () => { - const ids = await getMiniflareDurableObjectIds("OBJECT"); diff --git a/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx b/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx index 234a6c00e9b..fc720c97a87 100644 --- a/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx +++ b/src/content/docs/workers/testing/vitest-integration/migration-guides/migrate-from-unstable-dev.mdx @@ -27,14 +27,14 @@ it("dispatches fetch event", () => { }) ``` -With the Workers Vitest integration, you can accomplish the same goal using `SELF` from `cloudflare:test`. `SELF` is a [service binding](/workers/runtime-apis/bindings/service-bindings/) to the default export defined by the `main` option in your [Wrangler configuration file](/workers/wrangler/configuration/). This `main` Worker runs in the same isolate as tests so any global mocks will apply to it too. +With the Workers Vitest integration, you can accomplish the same goal using `exports` from `cloudflare:workers`. `exports.default` refers to the default export defined by the `main` option in your [Wrangler configuration file](/workers/wrangler/configuration/). This `main` Worker runs in the same isolate as tests so any global mocks will apply to it too. ```js -import { SELF } from "cloudflare:test"; +import { exports } from "cloudflare:workers"; import "../src/"; // Currently required to automatically rerun tests when `main` changes it("dispatches fetch event", async () => { - const response = await SELF.fetch("http://example.com"); + const response = await exports.default.fetch("http://example.com"); ... }); ``` @@ -55,19 +55,19 @@ await unstable_dev("src/index.ts", { With the Workers Vitest integration, you can now set this reference to a [Wrangler configuration file](/workers/wrangler/configuration/) in `vitest.config.js` for all of your tests: -```js null {5-7} -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { - configPath: "wrangler.toml", - }, - }, - }, - }, +```js {3-5} +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "wrangler.jsonc", + }, + }), + ], }); ---- ``` ## Test service Workers diff --git a/src/content/docs/workers/testing/vitest-integration/test-apis.mdx b/src/content/docs/workers/testing/vitest-integration/test-apis.mdx index fcb83721f99..b16c082146d 100644 --- a/src/content/docs/workers/testing/vitest-integration/test-apis.mdx +++ b/src/content/docs/workers/testing/vitest-integration/test-apis.mdx @@ -4,24 +4,24 @@ pcx_content_type: reference sidebar: order: 5 head: [] -description: Runtime helpers for writing tests, exported from the `cloudflare:test` module. +description: Runtime helpers for writing tests, exported from `cloudflare:workers` and `cloudflare:test`. --- -The Workers Vitest integration provides runtime helpers for writing tests in the `cloudflare:test` module. The `cloudflare:test` module is provided by the `@cloudflare/vitest-pool-workers` package, but can only be imported from test files that execute in the Workers runtime. +The Workers Vitest integration provides runtime helpers for writing tests. Some helpers are exported from the `cloudflare:workers` module, and others from the `cloudflare:test` module. Both modules are provided by the `@cloudflare/vitest-pool-workers` package, but can only be imported from test files that execute in the Workers runtime. -## `cloudflare:test` module definition +## `cloudflare:workers` exports -* env: import("cloudflare:test").ProvidedEnv +* env: import("cloudflare:workers").ProvidedEnv * Exposes the [`env` object](/workers/runtime-apis/handlers/fetch/#parameters) for use as the second argument passed to ES modules format exported handlers. This provides access to [bindings](/workers/runtime-apis/bindings/) that you have defined in your [Vitest configuration file](/workers/testing/vitest-integration/configuration/).
```js - import { env } from "cloudflare:test"; + import { env } from "cloudflare:workers"; it("uses binding", async () => { await env.KV_NAMESPACE.put("key", "value"); @@ -32,7 +32,7 @@ The Workers Vitest integration provides runtime helpers for writing tests in the To configure the type of this value, use an ambient module type: ```ts - declare module "cloudflare:test" { + declare module "cloudflare:workers" { interface ProvidedEnv { KV_NAMESPACE: KVNamespace; } @@ -41,51 +41,22 @@ The Workers Vitest integration provides runtime helpers for writing tests in the } ``` -* SELF: Fetcher +* exports: object - * [Service binding](/workers/runtime-apis/bindings/service-bindings/) to the default export defined in the `main` Worker. Use this to write integration tests against your Worker. The `main` Worker runs in the same isolate/context as tests so any global mocks will apply to it too. + * Provides access to the exports of the `main` Worker. Use `exports.default.fetch()` to write integration tests against your Worker's default export handler. The `main` Worker runs in the same isolate/context as tests so any global mocks will apply to it too. Unlike the previous `SELF` binding, `exports` does not expose Assets. To test assets, use [`startDevWorker()`](/workers/testing/unstable_startworker/).
```js - import { SELF } from "cloudflare:test"; + import { exports } from "cloudflare:workers"; it("dispatches fetch event", async () => { - const response = await SELF.fetch("https://example.com"); + const response = await exports.default.fetch("https://example.com"); expect(await response.text()).toMatchInlineSnapshot(...); }); ``` -* fetchMock: import("undici").MockAgent - - * Declarative interface for mocking outbound `fetch()` requests. Deactivated by default and reset before running each test file. Refer to [`undici`'s `MockAgent` documentation](https://undici.nodejs.org/#/docs/api/MockAgent) for more information. Note this only mocks `fetch()` requests for the current test runner Worker. Auxiliary Workers should mock `fetch()`es using the Miniflare `fetchMock`/`outboundService` options. Refer to [Configuration](/workers/testing/vitest-integration/configuration/#workerspooloptions) for more information. - -
- - ```js - import { fetchMock } from "cloudflare:test"; - import { beforeAll, afterEach, it, expect } from "vitest"; - - beforeAll(() => { - // Enable outbound request mocking... - fetchMock.activate(); - // ...and throw errors if an outbound request isn't mocked - fetchMock.disableNetConnect(); - }); - // Ensure we matched every mock we defined - afterEach(() => fetchMock.assertNoPendingInterceptors()); - - it("mocks requests", async () => { - // Mock the first request to `https://example.com` - fetchMock - .get("https://example.com") - .intercept({ path: "/" }) - .reply(200, "body"); - - const response = await fetch("https://example.com/"); - expect(await response.text()).toBe("body"); - }); - ``` +## `cloudflare:test` exports @@ -104,7 +75,8 @@ The Workers Vitest integration provides runtime helpers for writing tests in the
```ts - import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; + import { env } from "cloudflare:workers"; + import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; @@ -124,7 +96,8 @@ The Workers Vitest integration provides runtime helpers for writing tests in the
```ts - import { env, createScheduledController, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; + import { env } from "cloudflare:workers"; + import { createScheduledController, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; @@ -150,7 +123,8 @@ The Workers Vitest integration provides runtime helpers for writing tests in the
```ts - import { env, createMessageBatch, createExecutionContext, getQueueResult } from "cloudflare:test"; + import { env } from "cloudflare:workers"; + import { createMessageBatch, createExecutionContext, getQueueResult } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index.mjs"; @@ -201,7 +175,8 @@ The Workers Vitest integration provides runtime helpers for writing tests in the ``` ```ts - import { env, runInDurableObject } from "cloudflare:test"; + import { env } from "cloudflare:workers"; + import { runInDurableObject } from "cloudflare:test"; import { it, expect } from "vitest"; import { Counter } from "./index.ts"; @@ -228,12 +203,13 @@ The Workers Vitest integration provides runtime helpers for writing tests in the * listDurableObjectIds(namespace:DurableObjectNamespace): Promise\ - * Gets the IDs of all objects that have been created in the `namespace`. Respects `isolatedStorage` if enabled, meaning objects created in a different test will not be returned. + * Gets the IDs of all objects that have been created in the `namespace`. Respects per-file storage isolation, meaning objects created in a different test file will not be returned.
```ts - import { env, listDurableObjectIds } from "cloudflare:test"; + import { env } from "cloudflare:workers"; + import { listDurableObjectIds } from "cloudflare:test"; import { it, expect } from "vitest"; it("increments count", async () => { @@ -263,9 +239,9 @@ The Workers Vitest integration provides runtime helpers for writing tests in the -:::caution[Workflows with `isolatedStorage`] +:::caution[Workflows with storage isolation] -To ensure proper test isolation in Workflows with isolated storage, introspectors should be disposed at the end of each test. +To ensure proper test isolation in Workflows with per-file storage isolation, introspectors should be disposed at the end of each test. This is accomplished by either: * Using an `await using` statement on the introspector. * Explicitly calling the introspector `dispose()` method. @@ -283,7 +259,8 @@ Available in `@cloudflare/vitest-pool-workers` version **0.9.0**!
```ts - import { env, introspectWorkflowInstance } from "cloudflare:test"; + import { env } from "cloudflare:workers"; + import { introspectWorkflowInstance } from "cloudflare:test"; it("should disable all sleeps, mock an event and complete", async () => { // 1. CONFIGURATION @@ -321,7 +298,8 @@ Available in `@cloudflare/vitest-pool-workers` version **0.9.0**!
```ts - import { env, introspectWorkflow, SELF } from "cloudflare:test"; + import { env, exports } from "cloudflare:workers"; + import { introspectWorkflow } from "cloudflare:test"; it("should disable all sleeps, mock an event and complete", async () => { // 1. CONFIGURATION @@ -351,7 +329,7 @@ Available in `@cloudflare/vitest-pool-workers` version **0.9.0**! The workflow instance doesn't have to be created directly inside the test. The introspector will capture **all** instances created after it is initialized. For example, you could trigger the creation of **one or multiple** instances via a single `fetch` event to your Worker: ```js // This also works for the EXECUTION phase: - await SELF.fetch("https://example.com/trigger-workflows"); + await exports.default.fetch("https://example.com/trigger-workflows"); ``` * The returned `WorkflowIntrospector` object has the following methods: @@ -371,7 +349,8 @@ Available in `@cloudflare/vitest-pool-workers` version **0.9.0**!
```ts - import { env, introspectWorkflowInstance } from "cloudflare:test"; + import { env } from "cloudflare:workers"; + import { introspectWorkflowInstance } from "cloudflare:test"; // This example showcases explicit disposal it("should apply all modifier functions", async () => { diff --git a/src/content/docs/workers/testing/vitest-integration/write-your-first-test.mdx b/src/content/docs/workers/testing/vitest-integration/write-your-first-test.mdx index dc52c1fa599..c135ce09566 100644 --- a/src/content/docs/workers/testing/vitest-integration/write-your-first-test.mdx +++ b/src/content/docs/workers/testing/vitest-integration/write-your-first-test.mdx @@ -26,33 +26,32 @@ First, make sure that: - Vitest and `@cloudflare/vitest-pool-workers` are installed in your project as dev dependencies :::note - Currently, the `@cloudflare/vitest-pool-workers` package _only_ works with Vitest 2.0.x - 3.2.x. + The `@cloudflare/vitest-pool-workers` package requires Vitest 4.1 or later. ::: ## Define Vitest configuration -In your `vitest.config.ts` file, use `defineWorkersConfig` to configure the Workers Vitest integration. +In your `vitest.config.ts` file, use the `cloudflareTest()` plugin to configure the Workers Vitest integration. You can use your Worker configuration from your [Wrangler config file](/workers/wrangler/configuration/) by specifying it with `wrangler.configPath`. ```ts title = vitest.config.ts -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], }); ``` @@ -61,17 +60,15 @@ You can also override or define additional configuration using the `miniflare` k For example, this configuration would add a KV namespace `TEST_NAMESPACE` that was only accessed and modified in tests. ```js null {6-8} - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - miniflare: { - kvNamespaces: ["TEST_NAMESPACE"], - }, + export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + miniflare: { + kvNamespaces: ["TEST_NAMESPACE"], }, - }, - }, + }), + ], }); ``` @@ -95,7 +92,7 @@ You should also add the output of `wrangler types` to the `include` array so tha "compilerOptions": { "moduleResolution": "bundler", "types": [ - "@cloudflare/vitest-pool-workers", // provides `cloudflare:test` types + "@cloudflare/vitest-pool-workers", // provides `cloudflare:test` and `cloudflare:workers` types ], }, "include": [ @@ -109,8 +106,8 @@ You should also add the output of `wrangler types` to the `include` array so tha You also need to define the type of the `env` object that is provided to your tests. Create an `env.d.ts` file in your tests folder, and declare the `ProvidedEnv` interface by extending the `Env` interface that is generated by `wrangler types`. ```ts title="test/env.d.ts" -declare module "cloudflare:test" { - // ProvidedEnv controls the type of `import("cloudflare:test").env` +declare module "cloudflare:workers" { + // ProvidedEnv controls the type of `import("cloudflare:workers").env` interface ProvidedEnv extends Env {} } ``` @@ -141,8 +138,8 @@ By importing the Worker we can write a unit test for its `fetch` handler. ```ts + import { env } from "cloudflare:workers"; import { - env, createExecutionContext, waitOnExecutionContext, } from "cloudflare:test"; @@ -173,16 +170,16 @@ By importing the Worker we can write a unit test for its `fetch` handler. ### Integration tests -You can use the SELF fetcher provided by the `cloudflare:test` to write an integration test. This is a service binding to the default export defined in the main Worker. +You can use the `exports` object provided by `cloudflare:workers` to write an integration test. `exports.default.fetch()` calls the default export handler defined in the main Worker. ```ts - import { SELF } from "cloudflare:test"; + import { exports } from "cloudflare:workers"; import { describe, it, expect } from "vitest"; describe("Hello World worker", () => { it("responds with not found and proper status for /404", async () => { - const response = await SELF.fetch("http://example.com/404"); + const response = await exports.default.fetch("http://example.com/404"); expect(response.status).toBe(404); expect(await response.text()).toBe("Not found"); }); @@ -192,7 +189,7 @@ You can use the SELF fetcher provided by the `cloudflare:test` to write an integ -When using `SELF` for integration tests, your Worker code runs in the same context as the test runner. This means you can use global mocks to control your Worker, but also means your Worker uses the subtly different module resolution behavior provided by Vite. +When using `exports.default.fetch()` for integration tests, your Worker code runs in the same context as the test runner. This means you can use global mocks to control your Worker, but also means your Worker uses the subtly different module resolution behavior provided by Vite. Usually this is not a problem, but to run your Worker in a fresh environment that is as close to production as possible, you can use an auxiliary Worker. Refer to [this example](https://github.com/cloudflare/workers-sdk/blob/main/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/vitest.config.ts) for how to set up integration tests using auxiliary Workers. However, using auxiliary Workers comes with [limitations](/workers/testing/vitest-integration/configuration/#workerspooloptions) that you should be aware of. ## Related resources