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 `