Skip to content

Add miniflare docs #18690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 20 additions & 27 deletions src/content/docs/workers/testing/integration-testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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.


:::

<Render file="testing-pages-functions" product="workers" />

## 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.
Original file line number Diff line number Diff line change
@@ -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",
],
});
```
101 changes: 101 additions & 0 deletions src/content/docs/workers/testing/miniflare/core/fetch.mdx
Original file line number Diff line number Diff line change
@@ -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
```
8 changes: 8 additions & 0 deletions src/content/docs/workers/testing/miniflare/core/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Core
order: 2
---

import { DirectoryListing } from "~/components";

<DirectoryListing path="/core" />
69 changes: 69 additions & 0 deletions src/content/docs/workers/testing/miniflare/core/modules.md
Original file line number Diff line number Diff line change
@@ -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...");
},
};
```

<Aside type="warning" header="Warning">

String scripts via the `script` option are supported using
the `modules` format, but you cannot import other modules using them. You must
use a script file via the `scriptPath` option for this.

</Aside>

## 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"] },
];
```
Loading
Loading