diff --git a/src/content/docs/workers/testing/integration-testing.mdx b/src/content/docs/workers/testing/integration-testing.mdx
index 30e4484f911d911..a23e47eed47fa78 100644
--- a/src/content/docs/workers/testing/integration-testing.mdx
+++ b/src/content/docs/workers/testing/integration-testing.mdx
@@ -5,26 +5,25 @@ sidebar:
order: 3
head: []
description: Test multiple units of your Worker working together.
-
---
-import { Render } from "~/components"
+import { Render } from "~/components";
Integration tests test multiple units of your Worker together by sending HTTP requests to your Worker and asserting on the HTTP responses. As an example, consider the following Worker:
```js
export function add(a, b) {
- return a + b;
+ return a + b;
}
export default {
- async fetch(request) {
- const url = new URL(request.url);
- const a = parseInt(url.searchParams.get("a"));
- const b = parseInt(url.searchParams.get("b"));
- return new Response(add(a, b));
- }
-}
+ async fetch(request) {
+ const url = new URL(request.url);
+ const a = parseInt(url.searchParams.get("a"));
+ const b = parseInt(url.searchParams.get("b"));
+ return new Response(add(a, b));
+ },
+};
```
An integration test for this Worker might look like the following example:
@@ -75,16 +74,14 @@ it("dispatches fetch event", async () => {
});
```
-Instead of running the Worker-under-test in the same Worker as the test runner like `SELF`, this example defines the Worker-under-test as an *auxiliary* Worker. This means the Worker runs in a separate isolate to the test runner, with a different global scope. The Worker-under-test runs in an environment closer to production, but Vite transformations and hot-module-reloading aren't applied to the Workerβyou must compile your TypeScript to JavaScript beforehand.
+Instead of running the Worker-under-test in the same Worker as the test runner like `SELF`, this example defines the Worker-under-test as an _auxiliary_ Worker. This means the Worker runs in a separate isolate to the test runner, with a different global scope. The Worker-under-test runs in an environment closer to production, but Vite transformations and hot-module-reloading aren't applied to the Workerβyou must compile your TypeScript to JavaScript beforehand.
Auxiliary Workers cannot be configured from `wrangler.toml` files. You must use Miniflare [`WorkerOptions`](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare#interface-workeroptions) in `vitest.config.ts`.
:::note
-
This method is less recommended than `SELF` for integration tests because of its developer experience. However, it can be useful when you are testing multiple Workers. You can define multiple Workers by different names in `vitest.config.ts` and reference them via `env`.
-
:::
## Wrangler's `unstable_dev()` API
@@ -97,51 +94,47 @@ import { unstable_dev } from "wrangler";
const worker = await unstable_dev("./index.mjs");
try {
- const response = await worker.fetch("/?a=1&b=2");
- assert.strictEqual(await response.text(), "3");
+ const response = await worker.fetch("/?a=1&b=2");
+ assert.strictEqual(await response.text(), "3");
} finally {
- await worker.stop();
+ await worker.stop();
}
```
:::note
-
If you have been using `unstable_dev()` for integration testing and want to migrate to Cloudflare's Vitest integration, refer to the [Migrate from `unstable_dev` migration guide](/workers/testing/vitest-integration/get-started/migrate-from-unstable-dev/) for more information.
-
:::
## Miniflare's API
-If you would like to write integration tests for multiple Workers, need direct access to [bindings](/workers/runtime-apis/bindings/) outside your Worker in tests, or have another advanced use case, consider using [Miniflare's API](https://github.com/cloudflare/workers-sdk/blob/main/packages/miniflare/README.md) directly. Miniflare is the foundation for the other testing tools on this page, exposing a JavaScript API for the [`workerd` runtime](https://github.com/cloudflare/workerd) and local simulators for the other Developer Platform products. Unlike `unstable_dev()`, Miniflare does not automatically load options from your Wrangler configuration file.
+If you would like to write integration tests for multiple Workers, need direct access to [bindings](/workers/runtime-apis/bindings/) outside your Worker in tests, or have another advanced use case, consider using [Miniflare's API](/workers/testing/miniflare) directly. Miniflare is the foundation for the other testing tools on this page, exposing a JavaScript API for the [`workerd` runtime](https://github.com/cloudflare/workerd) and local simulators for the other Developer Platform products. Unlike `unstable_dev()`, Miniflare does not automatically load options from your Wrangler configuration file.
```js
import assert from "node:assert";
import { Miniflare } from "miniflare";
const mf = new Miniflare({
- modules: true,
- scriptPath: "./index.mjs",
+ modules: true,
+ scriptPath: "./index.mjs",
});
try {
- const response = await mf.dispatchFetch("http://example.com/?a=1&b=2");
- assert.strictEqual(await response.text(), "3");
+ const response = await mf.dispatchFetch("http://example.com/?a=1&b=2");
+ assert.strictEqual(await response.text(), "3");
} finally {
- await mf.dispose();
+ await mf.dispose();
}
```
:::note
-
If you have been using the test environments from Miniflare 2 for integration testing and want to migrate to Cloudflare's Vitest integration, refer to the [Migrate from Miniflare 2 migration guide](/workers/testing/vitest-integration/get-started/migrate-from-miniflare-2/) for more information.
-
:::
## Related Resources
-* [Recipes](/workers/testing/vitest-integration/recipes/) - Example integration tests for Workers using the Workers Vitest integration.
+- [Recipes](/workers/testing/vitest-integration/recipes/) - Example integration tests for Workers using the Workers Vitest integration.
diff --git a/src/content/docs/workers/testing/miniflare/core/compatibility.mdx b/src/content/docs/workers/testing/miniflare/core/compatibility.mdx
new file mode 100644
index 000000000000000..8fc17e2368d0ecd
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/compatibility.mdx
@@ -0,0 +1,32 @@
+---
+order: 8
+title: "π Compatibility Dates"
+---
+
+- [Compatibility Dates Reference](/workers/configuration/compatibility-dates)
+
+## Compatibility Dates
+
+Miniflare uses compatibility dates to opt-into backwards-incompatible changes
+from a specific date. If one isn't set, it will default to some time far in the
+past.
+
+```js
+const mf = new Miniflare({
+ compatibilityDate: "2021-11-12",
+});
+```
+
+## Compatibility Flags
+
+Miniflare also lets you opt-in/out of specific changes using compatibility
+flags:
+
+```js
+const mf = new Miniflare({
+ compatibilityFlags: [
+ "formdata_parser_supports_files",
+ "durable_object_fetch_allows_relative_url",
+ ],
+});
+```
diff --git a/src/content/docs/workers/testing/miniflare/core/fetch.mdx b/src/content/docs/workers/testing/miniflare/core/fetch.mdx
new file mode 100644
index 000000000000000..3308232a39496d8
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/fetch.mdx
@@ -0,0 +1,101 @@
+---
+order: 0
+title: "π¨ Fetch Events"
+---
+
+- [`FetchEvent` Reference](/workers/runtime-apis/handlers/fetch/)
+
+## HTTP Requests
+
+Whenever an HTTP request is made, a `Request` object is dispatched to your worker, then the generated `Response` is returned. The
+`Request` object will include a
+[`cf` object](/workers/runtime-apis/request#incomingrequestcfproperties).
+Miniflare will log the method, path, status, and the time it took to respond.
+
+If the Worker throws an error whilst generating a response, an error page
+containing the stack trace is returned instead.
+
+## Dispatching Events
+
+When using the API, the `dispatchFetch` function can be used to dispatch `fetch`
+events to your Worker. This can be used for testing responses. `dispatchFetch`
+has the same API as the regular `fetch` method: it either takes a `Request`
+object, or a URL and optional `RequestInit` object:
+
+```js
+import { Miniflare, Request } from "miniflare";
+
+const mf = new Miniflare({
+ modules: true,
+ script: `
+ export default {
+ async fetch(request, env, ctx) {
+ const body = JSON.stringify({
+ url: event.request.url,
+ header: event.request.headers.get("X-Message"),
+ });
+ return new Response(body, {
+ headers: { "Content-Type": "application/json" },
+ });
+ })
+ }
+ `,
+});
+
+let res = await mf.dispatchFetch("http://localhost:8787/");
+console.log(await res.json()); // { url: "http://localhost:8787/", header: null }
+
+res = await mf.dispatchFetch("http://localhost:8787/1", {
+ headers: { "X-Message": "1" },
+});
+console.log(await res.json()); // { url: "http://localhost:8787/1", header: "1" }
+
+res = await mf.dispatchFetch(
+ new Request("http://localhost:8787/2", {
+ headers: { "X-Message": "2" },
+ }),
+);
+console.log(await res.json()); // { url: "http://localhost:8787/2", header: "2" }
+```
+
+When dispatching events, you are responsible for adding
+[`CF-*` headers](https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-)
+and the
+[`cf` object](/workers/runtime-apis/request#incomingrequestcfproperties).
+This lets you control their values for testing:
+
+```js
+const res = await mf.dispatchFetch("http://localhost:8787", {
+ headers: {
+ "CF-IPCountry": "GB",
+ },
+ cf: {
+ country: "GB",
+ },
+});
+```
+
+## Upstream
+
+Miniflare will call each `fetch` listener until a response is returned. If no
+response is returned, or an exception is thrown and `passThroughOnException()`
+has been called, the response will be fetched from the specified upstream
+instead:
+
+```js
+import { Miniflare } from "miniflare";
+
+const mf = new Miniflare({
+ script: `
+ addEventListener("fetch", (event) => {
+ event.passThroughOnException();
+ throw new Error();
+ });
+ `,
+ upstream: "https://miniflare.dev",
+});
+// If you don't use the same upstream URL when dispatching, Miniflare will
+// rewrite it to match the upstream
+const res = await mf.dispatchFetch("https://miniflare.dev/core/fetch");
+console.log(await res.text()); // Source code of this page
+```
diff --git a/src/content/docs/workers/testing/miniflare/core/index.mdx b/src/content/docs/workers/testing/miniflare/core/index.mdx
new file mode 100644
index 000000000000000..b93bf22cd3d1f2f
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/index.mdx
@@ -0,0 +1,8 @@
+---
+title: Core
+order: 2
+---
+
+import { DirectoryListing } from "~/components";
+
+
diff --git a/src/content/docs/workers/testing/miniflare/core/modules.md b/src/content/docs/workers/testing/miniflare/core/modules.md
new file mode 100644
index 000000000000000..1ad63849f2895a1
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/modules.md
@@ -0,0 +1,69 @@
+---
+order: 3
+title: "π Modules"
+---
+
+- [Modules Reference](/workers/reference/migrate-to-module-workers/)
+
+## Enabling Modules
+
+Miniflare supports both the traditional `service-worker` and the newer `modules` formats for writing workers. To use the `modules` format, enable it with:
+
+```js
+const mf = new Miniflare({
+ modules: true,
+});
+```
+
+You can then use `modules` worker scripts like the following:
+
+```js
+export default {
+ async fetch(request, env, ctx) {
+ // - `request` is the incoming `Request` instance
+ // - `env` contains bindings, KV namespaces, Durable Objects, etc
+ // - `ctx` contains `waitUntil` and `passThroughOnException` methods
+ return new Response("Hello Miniflare!");
+ },
+ async scheduled(controller, env, ctx) {
+ // - `controller` contains `scheduledTime` and `cron` properties
+ // - `env` contains bindings, KV namespaces, Durable Objects, etc
+ // - `ctx` contains the `waitUntil` method
+ console.log("Doing something scheduled...");
+ },
+};
+```
+
+
+
+## Module Rules
+
+Miniflare supports all module types: `ESModule`, `CommonJS`, `Text`, `Data` and
+`CompiledWasm`. You can specify additional module resolution rules as follows:
+
+```js
+const mf = new Miniflare({
+ modulesRules: [
+ { type: "ESModule", include: ["**/*.js"], fallthrough: true },
+ { type: "Text", include: ["**/*.txt"] },
+ ],
+});
+```
+
+### Default Rules
+
+The following rules are automatically added to the end of your modules rules
+list. You can override them by specifying rules matching the same `globs`:
+
+```js
+[
+ { type: "ESModule", include: ["**/*.mjs"] },
+ { type: "CommonJS", include: ["**/*.js", "**/*.cjs"] },
+];
+```
diff --git a/src/content/docs/workers/testing/miniflare/core/multiple-workers.md b/src/content/docs/workers/testing/miniflare/core/multiple-workers.md
new file mode 100644
index 000000000000000..598e93b25367c69
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/multiple-workers.md
@@ -0,0 +1,122 @@
+---
+order: 9
+title: "π Multiple Workers"
+---
+
+Miniflare allows you to run multiple workers in the same instance. All Workers can be defined at the same level, using the `workers` option.
+
+Here's an example that uses a service binding to increment a value in a shared KV namespace:
+
+```js
+import { Miniflare, Response } from "miniflare";
+
+const message = "The count is ";
+const mf = new Miniflare({
+ // Options shared between workers such as HTTP and persistence configuration
+ // should always be defined at the top level.
+ host: "0.0.0.0",
+ port: 8787,
+ kvPersist: true,
+
+ workers: [
+ {
+ name: "worker",
+ kvNamespaces: { COUNTS: "counts" },
+ serviceBindings: {
+ INCREMENTER: "incrementer",
+ // Service bindings can also be defined as custom functions, with access
+ // to anything defined outside Miniflare.
+ async CUSTOM(request) {
+ // `request` is the incoming `Request` object.
+ return new Response(message);
+ },
+ },
+ modules: true,
+ script: `export default {
+ async fetch(request, env, ctx) {
+ // Get the message defined outside
+ const response = await env.CUSTOM.fetch("http://host/");
+ const message = await response.text();
+
+ // Increment the count 3 times
+ await env.INCREMENTER.fetch("http://host/");
+ await env.INCREMENTER.fetch("http://host/");
+ await env.INCREMENTER.fetch("http://host/");
+ const count = await env.COUNTS.get("count");
+
+ return new Response(message + count);
+ }
+ }`,
+ },
+ {
+ name: "incrementer",
+ // Note we're using the same `COUNTS` namespace as before, but binding it
+ // to `NUMBERS` instead.
+ kvNamespaces: { NUMBERS: "counts" },
+ // Worker formats can be mixed-and-matched
+ script: `addEventListener("fetch", (event) => {
+ event.respondWith(handleRequest());
+ })
+ async function handleRequest() {
+ const count = parseInt((await NUMBERS.get("count")) ?? "0") + 1;
+ await NUMBERS.put("count", count.toString());
+ return new Response(count.toString());
+ }`,
+ },
+ ],
+});
+const res = await mf.dispatchFetch("http://localhost");
+console.log(await res.text()); // "The count is 3"
+await mf.dispose();
+```
+
+## Routing
+
+You can enable routing by specifying `routes` via the API,
+using the
+[standard route syntax](/workers/configuration/routing/routes/#matching-behavior).
+Note port numbers are ignored:
+
+```js
+const mf = new Miniflare({
+ workers: [
+ {
+ scriptPath: "./api/worker.js",
+ routes: ["http://127.0.0.1/api*", "api.mf/*"],
+ },
+ ],
+});
+```
+
+When using hostnames that aren't `localhost` or `127.0.0.1`, you
+may need to edit your computer's `hosts` file, so those hostnames resolve to
+`localhost`. On Linux and macOS, this is usually at `/etc/hosts`. On Windows,
+it's at `C:\Windows\System32\drivers\etc\hosts`. For the routes above, we would
+need to append the following entries to the file:
+
+```
+127.0.0.1 miniflare.test
+127.0.0.1 api.mf
+```
+
+Alternatively, you can customise the `Host` header when sending the request:
+
+```sh
+# Dispatches to the "api" worker
+$ curl "http://localhost:8787/todos/update/1" -H "Host: api.mf"
+```
+
+When using the API, Miniflare will use the request's URL to determine which
+Worker to dispatch to.
+
+```js
+// Dispatches to the "api" worker
+const res = await mf.dispatchFetch("http://api.mf/todos/update/1", { ... });
+```
+
+## Durable Objects
+
+Miniflare supports the `script_name` option for accessing Durable Objects
+exported by other scripts. See
+[π Durable Objects](/workers/testing/miniflare/storage/durable-objects#using-a-class-exported-by-another-script)
+for more details.
diff --git a/src/content/docs/workers/testing/miniflare/core/queues.md b/src/content/docs/workers/testing/miniflare/core/queues.md
new file mode 100644
index 000000000000000..1b121e97c7314cd
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/queues.md
@@ -0,0 +1,73 @@
+---
+order: 8
+title: "π₯ Queues"
+---
+
+- [Queues Reference](/queues/)
+
+## Producers
+
+Specify Queue producers to add to your environment as follows:
+
+```js
+const mf = new Miniflare({
+ queueProducers: { MY_QUEUE: "my-queue" },
+ queueProducers: ["MY_QUEUE"], // If binding and queue names are the same
+});
+```
+
+## Consumers
+
+Specify Workers to consume messages from your Queues as follows:
+
+```js
+const mf = new Miniflare({
+ queueConsumers: {
+ "my-queue": {
+ maxBatchSize: 5, // default: 5
+ maxBatchTimeout: 1 /* second(s) */, // default: 1
+ maxRetries: 2, // default: 2
+ deadLetterQueue: "my-dead-letter-queue", // default: none
+ },
+ },
+ queueConsumers: ["my-queue"], // If using default consumer options
+});
+```
+
+## Manipulating Outside Workers
+
+For testing, it can be valuable to interact with Queues outside a Worker. You can do this by using the `workers` option to run multiple Workers in the same instance:
+
+```js
+const mf = new Miniflare({
+ workers: [
+ {
+ name: "a",
+ modules: true,
+ script: `
+ export default {
+ async fetch(request, env, ctx) {
+ await env.QUEUE.send(await request.text());
+ }
+ }
+ `,
+ queueProducers: { QUEUE: "my-queue" },
+ },
+ {
+ name: "b",
+ modules: true,
+ script: `
+ export default {
+ async queue(batch, env, ctx) {
+ console.log(batch);
+ }
+ }
+ `,
+ queueConsumers: { "my-queue": { maxBatchTimeout: 1 } },
+ },
+ ],
+});
+
+const queue = await mf.getQueueProducer("QUEUE", "a"); // Get from worker "a"
+await queue.send("message"); // Logs "message" 1 second later
+```
diff --git a/src/content/docs/workers/testing/miniflare/core/scheduled.md b/src/content/docs/workers/testing/miniflare/core/scheduled.md
new file mode 100644
index 000000000000000..a8a309fe7f12aa3
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/scheduled.md
@@ -0,0 +1,72 @@
+---
+order: 1
+title: "β° Scheduled Events"
+---
+
+- [`ScheduledEvent` Reference](/workers/runtime-apis/handlers/scheduled/)
+
+## Cron Triggers
+
+`scheduled` events are automatically dispatched according to the specified cron
+triggers:
+
+```js
+const mf = new Miniflare({
+ crons: ["15 * * * *", "45 * * * *"],
+});
+```
+
+## HTTP Triggers
+
+Because waiting for cron triggers is annoying, you can also make HTTP requests
+to `/cdn-cgi/mf/scheduled` to trigger `scheduled` events:
+
+```sh
+$ curl "http://localhost:8787/cdn-cgi/mf/scheduled"
+```
+
+To simulate different values of `scheduledTime` and `cron` in the dispatched
+event, use the `time` and `cron` query parameters:
+
+```sh
+$ curl "http://localhost:8787/cdn-cgi/mf/scheduled?time=1000"
+$ curl "http://localhost:8787/cdn-cgi/mf/scheduled?cron=*+*+*+*+*"
+```
+
+## Dispatching Events
+
+When using the API, the `getWorker` function can be used to dispatch
+`scheduled` events to your Worker. This can be used for testing responses. It
+takes optional `scheduledTime` and `cron` parameters, which default to the
+current time and the empty string respectively. It will return a promise which
+resolves to an array containing data returned by all waited promises:
+
+```js
+import { Miniflare } from "miniflare";
+
+const mf = new Miniflare({
+ modules: true,
+ script: `
+ export default {
+ async scheduled(controller, env, ctx) {
+ const lastScheduledController = controller;
+ if (controller.cron === "* * * * *") controller.noRetry();
+ }
+ }
+ `,
+});
+
+const worker = await mf.getWorker();
+
+let scheduledResult = await worker.scheduled({
+ cron: "* * * * *",
+});
+console.log(scheduledResult); // { outcome: 'ok', noRetry: true }
+
+scheduledResult = await worker.scheduled({
+ scheduledTime: new Date(1000),
+ cron: "30 * * * *",
+});
+
+console.log(scheduledResult); // { outcome: 'ok', noRetry: false }
+```
diff --git a/src/content/docs/workers/testing/miniflare/core/standards.md b/src/content/docs/workers/testing/miniflare/core/standards.md
new file mode 100644
index 000000000000000..869eadc98e95a20
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/standards.md
@@ -0,0 +1,65 @@
+---
+order: 6
+title: "πΈ Web Standards"
+---
+
+- [Web Standards Reference](/workers/runtime-apis/web-standards)
+- [Encoding Reference](/workers/runtime-apis/encoding)
+- [Fetch Reference](/workers/runtime-apis/fetch)
+- [Request Reference](/workers/runtime-apis/request)
+- [Response Reference](/workers/runtime-apis/response)
+- [Streams Reference](/workers/runtime-apis/streams)
+- [Web Crypto Reference](/workers/runtime-apis/web-crypto)
+
+## Mocking Outbound `fetch` Requests
+
+When using the API, Miniflare allows you to substitute custom `Response`s for
+`fetch()` calls using `undici`'s
+[`MockAgent` API](https://undici.nodejs.org/#/docs/api/MockAgent?id=mockagentgetorigin).
+This is useful for testing Workers that make HTTP requests to other services. To
+enable `fetch` mocking, create a
+[`MockAgent`](https://undici.nodejs.org/#/docs/api/MockAgent?id=mockagentgetorigin)
+using the `createFetchMock()` function, then set this using the `fetchMock`
+option.
+
+```js
+import { Miniflare, createFetchMock } from "miniflare";
+
+// Create `MockAgent` and connect it to the `Miniflare` instance
+const fetchMock = createFetchMock();
+const mf = new Miniflare({
+ modules: true,
+ script: `
+ export default {
+ async fetch(request, env, ctx) {
+ const res = await fetch("https://example.com/thing");
+ const text = await res.text();
+ return new Response(\`response:\${text}\`);
+ }
+ }
+ `,
+ fetchMock,
+});
+
+// Throw when no matching mocked request is found
+// (see https://undici.nodejs.org/#/docs/api/MockAgent?id=mockagentdisablenetconnect)
+fetchMock.disableNetConnect();
+
+// Mock request to https://example.com/thing
+// (see https://undici.nodejs.org/#/docs/api/MockAgent?id=mockagentgetorigin)
+const origin = fetchMock.get("https://example.com");
+// (see https://undici.nodejs.org/#/docs/api/MockPool?id=mockpoolinterceptoptions)
+origin
+ .intercept({ method: "GET", path: "/thing" })
+ .reply(200, "Mocked response!");
+
+const res = await mf.dispatchFetch("http://localhost:8787/");
+console.log(await res.text()); // "response:Mocked response!"
+```
+
+## Subrequests
+
+Miniflare does not support limiting the amount of
+[subrequests](/workers/platform/limits#account-plan-limits).
+Please keep this in mind if you make a large amount of subrequests from your
+Worker.
diff --git a/src/content/docs/workers/testing/miniflare/core/variables-secrets.md b/src/content/docs/workers/testing/miniflare/core/variables-secrets.md
new file mode 100644
index 000000000000000..b9a4ff947f96cbf
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/variables-secrets.md
@@ -0,0 +1,33 @@
+---
+order: 2
+title: "π Variables and Secrets"
+---
+
+## Bindings
+
+Variable and secrets are bound as follows:
+
+```js
+const mf = new Miniflare({
+ bindings: {
+ KEY1: "value1",
+ KEY2: "value2",
+ },
+});
+```
+
+## Text and Data Blobs
+
+Text and data blobs can be loaded from files. File contents will be read and
+bound as `string`s and `ArrayBuffer`s respectively.
+
+```js
+const mf = new Miniflare({
+ textBlobBindings: { TEXT: "text.txt" },
+ dataBlobBindings: { DATA: "data.bin" },
+});
+```
+
+## Globals
+
+Injecting arbitrary globals is not supported by [workerd](https://github.com/cloudflare/workerd). If you're using a service Worker, bindings will be injected as globals, but these must be JSON-serialisable.
diff --git a/src/content/docs/workers/testing/miniflare/core/web-sockets.md b/src/content/docs/workers/testing/miniflare/core/web-sockets.md
new file mode 100644
index 000000000000000..b5076171739c6c7
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/core/web-sockets.md
@@ -0,0 +1,57 @@
+---
+order: 4
+title: "βοΈ WebSockets"
+---
+
+- [WebSockets Reference](/workers/runtime-apis/websockets)
+- [Using WebSockets](/workers/examples/websockets/)
+
+## Server
+
+Miniflare will always upgrade Web Socket connections. The Worker must respond
+with a status `101 Switching Protocols` response including a `webSocket`. For
+example, the Worker below implements an echo WebSocket server:
+
+```js
+export default {
+ fetch(request) {
+ const [client, server] = Object.values(new WebSocketPair());
+
+ server.accept();
+ server.addEventListener("message", (event) => {
+ server.send(event.data);
+ });
+
+ return new Response(null, {
+ status: 101,
+ webSocket: client,
+ });
+ },
+};
+```
+
+When using `dispatchFetch`, you are responsible for handling WebSockets by using
+the `webSocket` property on `Response`. As an example, if the above worker
+script was stored in `echo.mjs`:
+
+```js {13-17}
+import { Miniflare } from "miniflare";
+
+const mf = new Miniflare({
+ modules: true,
+ scriptPath: "echo.mjs",
+});
+
+const res = await mf.dispatchFetch("https://example.com", {
+ headers: {
+ Upgrade: "websocket",
+ },
+});
+const webSocket = res.webSocket;
+webSocket.accept();
+webSocket.addEventListener("message", (event) => {
+ console.log(event.data);
+});
+
+webSocket.send("Hello!"); // Above listener logs "Hello!"
+```
diff --git a/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-node-add.png b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-node-add.png
new file mode 100644
index 000000000000000..b66aa985c4fddbf
Binary files /dev/null and b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-node-add.png differ
diff --git a/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-node-run.png b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-node-run.png
new file mode 100644
index 000000000000000..eb534f43d7aadad
Binary files /dev/null and b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-node-run.png differ
diff --git a/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-npm.png b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-npm.png
new file mode 100644
index 000000000000000..e0c42c96f7254ff
Binary files /dev/null and b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-npm.png differ
diff --git a/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-settings.png b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-settings.png
new file mode 100644
index 000000000000000..f055b817d6c273c
Binary files /dev/null and b/src/content/docs/workers/testing/miniflare/developing/debugger-webstorm-settings.png differ
diff --git a/src/content/docs/workers/testing/miniflare/developing/debugger.md b/src/content/docs/workers/testing/miniflare/developing/debugger.md
new file mode 100644
index 000000000000000..41fee841426906b
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/developing/debugger.md
@@ -0,0 +1,67 @@
+---
+order: 4
+title: "π Attaching a Debugger"
+---
+
+:::caution
+This documentation describes breakpoint debugging when using Miniflare directly, which is only relevant for advanced use cases. Instead, most users should refer to the [Workers Observability documentation for how to set this up when using Wrangler](/workers/observability/dev-tools/breakpoints/).
+:::
+
+You can use regular Node.js tools to debug your Workers. Setting breakpoints,
+watching values and inspecting the call stack are all examples of things you can
+do with a debugger.
+
+## Visual Studio Code
+
+### Create configuration
+
+The easiest way to debug a Worker in VSCode is to create a new configuration.
+
+Open the **Run and Debug** menu in the VSCode activity bar and create a
+`.vscode/launch.json` file that contains the following:
+
+```json
+---
+filename: .vscode/launch.json
+---
+{
+ "configurations": [
+ {
+ "name": "Miniflare",
+ "type": "node",
+ "request": "attach",
+ "port": 9229,
+ "cwd": "/",
+ "resolveSourceMapLocations": null,
+ "attachExistingChildren": false,
+ "autoAttachChildProcesses": false,
+ }
+ ]
+}
+```
+
+From the **Run and Debug** menu in the activity bar, select the `Miniflare`
+configuration, and click the green play button to start debugging.
+
+## WebStorm
+
+Create a new configuration, by clicking **Add Configuration** in the top right.
+
+![WebStorm add configuration button](./debugger-webstorm-node-add.png)
+
+Click the **plus** button in the top left of the popup and create a new
+**Node.js/Chrome** configuration. Set the **Host** field to `localhost` and the
+**Port** field to `9229`. Then click **OK**.
+
+![WebStorm Node.js debug configuration](./debugger-webstorm-settings.png)
+
+With the new configuration selected, click the green debug button to start
+debugging.
+
+![WebStorm configuration debug button](./debugger-webstorm-node-run.png)
+
+## DevTools
+
+Breakpoints can also be added via the Workers DevTools. For more information,
+[read the guide](/workers/observability/dev-tools)
+in the Cloudflare Workers docs.
diff --git a/src/content/docs/workers/testing/miniflare/developing/index.mdx b/src/content/docs/workers/testing/miniflare/developing/index.mdx
new file mode 100644
index 000000000000000..ca5fa0345768377
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/developing/index.mdx
@@ -0,0 +1,8 @@
+---
+order: 4
+title: "Developing"
+---
+
+import { DirectoryListing } from "~/components";
+
+
diff --git a/src/content/docs/workers/testing/miniflare/developing/live-reload.md b/src/content/docs/workers/testing/miniflare/developing/live-reload.md
new file mode 100644
index 000000000000000..b0bf504c3d73c98
--- /dev/null
+++ b/src/content/docs/workers/testing/miniflare/developing/live-reload.md
@@ -0,0 +1,35 @@
+---
+order: 2
+title: β‘οΈ Live Reload
+---
+
+Miniflare automatically refreshes your browser when your Worker script
+changes when `liveReload` is set to `true`.
+
+```js
+const mf = new Miniflare({
+ liveReload: true,
+});
+```
+
+Miniflare will only inject the `