Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit c28c916

Browse files
committed
Add Jest environment docs
1 parent 9359109 commit c28c916

File tree

2 files changed

+231
-2
lines changed

2 files changed

+231
-2
lines changed

docs/.vitepress/config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ module.exports = {
88
"link",
99
{
1010
rel: "icon",
11-
href:
12-
"data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔥</text></svg>",
11+
href: "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔥</text></svg>",
1312
},
1413
],
1514
["meta", { property: "og:description", content: pkg.description }],
@@ -49,6 +48,7 @@ module.exports = {
4948
{ text: "🗺 Source Maps", link: "/source-maps.html" },
5049
{ text: "🕸 Web Standards", link: "/standards.html" },
5150
{ text: "📄 HTMLRewriter", link: "/html-rewriter.html" },
51+
{ text: "🤹 Jest Environment", link: "/jest.html" },
5252
],
5353
},
5454
{

docs/jest.md

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# 🤹 Jest Environment
2+
3+
Miniflare includes a custom Jest environment that allows you to run your unit
4+
tests within the Miniflare sandbox. Note that Jest &ge; 27 is required.
5+
6+
## Setup
7+
8+
The Miniflare environment isn't installed by default, install it and Jest with:
9+
10+
```shell
11+
$ npm install -D jest-environment-miniflare jest
12+
```
13+
14+
In the following examples, we'll assume your `package.json` contains
15+
`"type": "module"`, and that you're using a tool to bundle your worker. See
16+
[⚡️ Developing with esbuild](/esbuild.html) for an example.
17+
18+
To enable the Miniflare environment, set the
19+
[`testEnvironment` option](https://jestjs.io/docs/configuration#testenvironment-string)
20+
in your Jest configuration:
21+
22+
```js
23+
// jest.config.js
24+
export default {
25+
testEnvironment: "miniflare",
26+
// Configuration is automatically loaded from `.env`, `package.json` and
27+
// `wrangler.toml` files by default, but you can pass any additional Miniflare
28+
// API options here:
29+
testEnvironmentOptions: {
30+
bindings: { KEY: "value" },
31+
kvNamespaces: ["TEST_NAMESPACE"],
32+
},
33+
};
34+
```
35+
36+
## Writing and Running Tests
37+
38+
The Miniflare environment lets us import our worker's functions with regular
39+
`import` syntax. We can write a test the following worker like so:
40+
41+
```js
42+
// src/index.js
43+
addEventListener("fetch", (event) => {
44+
event.respondWith(handleRequest(event.request));
45+
});
46+
47+
// Assuming you've got a build tool that removes `export`s when you actually
48+
// deploy your worker
49+
export async function handleRequest(request) {
50+
return new Response(`URL: ${request.url} KEY: ${KEY}`);
51+
}
52+
```
53+
54+
```js
55+
// test/index.spec.js
56+
import { handleRequest } from "../src/index.js";
57+
58+
test("responds with url", async () => {
59+
const req = new Request("http://localhost/");
60+
const res = await handleRequest(req);
61+
expect(await res.text()).toBe("URL: http://localhost/ KEY: value");
62+
});
63+
```
64+
65+
Modules support is still experimental in Jest and requires the
66+
`--experimental-vm-modules` flag. To run this test:
67+
68+
```shell
69+
$ NODE_OPTIONS=--experimental-vm-modules npx jest
70+
```
71+
72+
## Isolated Storage
73+
74+
The Miniflare environment will use isolated storage for KV namespaces, caches,
75+
and Durable Objects in each test. This essentially means any changes you make in
76+
a test or `describe`-block are automatically undone afterwards. The isolated
77+
storage is copied from the parent `describe`-block, allowing you to seed data in
78+
`beforeAll` hooks.
79+
80+
As an example, consider the following tests:
81+
82+
```js
83+
// Gets the array
84+
async function get() {
85+
const jsonValue = await TEST_NAMESPACE.get("array");
86+
return JSON.parse(jsonValue ?? "[]");
87+
}
88+
89+
// Pushes an item onto the end of the array
90+
async function push(item) {
91+
const value = await get();
92+
value.push(item);
93+
await TEST_NAMESPACE.put("array", JSON.stringify(value));
94+
}
95+
96+
beforeAll(async () => {
97+
await push("beforeAll");
98+
});
99+
100+
beforeEach(async () => {
101+
// This runs in each tests' isolated storage environment
102+
await push("beforeEach");
103+
});
104+
105+
test("test 1", async () => {
106+
// This push(1) will only mutate the isolated environment
107+
await push(1);
108+
expect(await get()).toEqual(["beforeAll", "beforeEach", 1]);
109+
});
110+
111+
test("test 2", async () => {
112+
await push(2);
113+
// Note that push(1) from the previous test has been "undone"
114+
expect(await get()).toEqual(["beforeAll", "beforeEach", 2]);
115+
});
116+
117+
describe("describe", () => {
118+
beforeAll(async () => {
119+
await push("describe: beforeAll");
120+
});
121+
122+
beforeEach(async () => {
123+
await push("describe: beforeEach");
124+
});
125+
126+
test("test 3", async () => {
127+
await push(3);
128+
expect(await get()).toEqual([
129+
// All beforeAll's run before beforeEach's
130+
"beforeAll",
131+
"describe: beforeAll",
132+
"beforeEach",
133+
"describe: beforeEach",
134+
3,
135+
]);
136+
});
137+
138+
test("test 4", async () => {
139+
await push(4);
140+
expect(await get()).toEqual([
141+
"beforeAll",
142+
"describe: beforeAll",
143+
"beforeEach",
144+
"describe: beforeEach",
145+
4,
146+
]);
147+
});
148+
});
149+
```
150+
151+
Note that bindings (e.g. variables, KV namespaces, etc) are only included in the
152+
global scope when you're using a `service-worker` format worker. In `modules`
153+
mode, you can use the `getMiniflareBindings` global method:
154+
155+
```js
156+
const { TEST_NAMESPACE } = getMiniflareBindings();
157+
```
158+
159+
Note also that storage persistence options (`kvPersist`, `cachePersist`, and
160+
`durableObjectsPersist`) are ignored by the Miniflare Jest environment.
161+
162+
## Durable Objects
163+
164+
When testing Durable Objects, Miniflare needs to run your script itself to
165+
extract exported Durable Object classes. Miniflare should be able to auto-detect
166+
your script from your `package.json` or `wrangler.toml` file, but you can also
167+
set it manually in Jest configuration:
168+
169+
```js
170+
// src/index.mjs
171+
export class TestObject {
172+
constructor(state) {
173+
this.storage = state.storage;
174+
}
175+
176+
async fetch() {
177+
const count = (await this.storage.get("count")) + 1;
178+
this.storage.put("count", count);
179+
return new Response(count.toString());
180+
}
181+
}
182+
```
183+
184+
```js
185+
// jest.config.js
186+
export default {
187+
testEnvironment: "miniflare",
188+
testEnvironmentOptions: {
189+
modules: true,
190+
scriptPath: "./src/index.mjs",
191+
durableObjects: {
192+
TEST_OBJECT: "TestObject",
193+
},
194+
},
195+
};
196+
```
197+
198+
To access Durable Object storage in tests, use the
199+
`getMiniflareDurableObjectStorage` global method:
200+
201+
```js
202+
test("increments count", async () => {
203+
// Durable Objects requires modules mode so bindings aren't accessible via the
204+
// global scope
205+
const { TEST_OBJECT } = getMiniflareBindings();
206+
const id = TEST_OBJECT.newUniqueId();
207+
208+
// Seed Durable Object storage (isolated storage rules from above also apply)
209+
const storage = await getMiniflareDurableObjectStorage(id);
210+
await storage.put("count", 3);
211+
212+
// Increment the count
213+
const stub = TEST_OBJECT.get(id);
214+
const res = await stub.fetch("http://localhost/");
215+
expect(await res.text()).toBe("4");
216+
217+
// Check storage updated
218+
expect(await storage.get("count")).toBe(4);
219+
});
220+
```
221+
222+
<!--prettier-ignore-start-->
223+
::: warning
224+
Note that if you also import `../src/index.mjs` in your test, your script will
225+
be run twice, as Miniflare and Jest don't share a module cache. This usually
226+
won't be a problem, but be aware you may have issues with unique `Symbol()`s or
227+
`instanceof`s.
228+
:::
229+
<!--prettier-ignore-end-->

0 commit comments

Comments
 (0)