Skip to content

Migrate from Miniflare 2's test environments

Miniflare 2 provided custom environments for Jest and Vitest in the jest-environment-miniflare and vitest-environment-miniflare packages respectively. The @cloudflare/vitest-pool-workers package provides similar functionality using modern Miniflare versions and the workerd runtime. workerd is the same JavaScript/WebAssembly runtime that powers Cloudflare Workers. Using workerd practically eliminates behavior mismatches between your tests and deployed code. Refer to the Miniflare 3 announcement for more information.

Install the Workers Vitest integration

First, you will need to uninstall the old environment and install the new pool. Vitest environments can only customize the global scope, whereas pools can run tests using a completely different runtime. In this case, the pool runs your tests inside workerd instead of Node.js.

Terminal window
npm uninstall vitest-environment-miniflare
npm install --save-dev --save-exact vitest@2.1.8
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 for supported options and the Miniflare version 2 to 3 migration guide for more information. If you relied on configuration stored in a wrangler.toml file, set wrangler.configPath too.

import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
export default defineWorkersConfig({
test: {
environment: "miniflare",
environmentOptions: { ... },
poolOptions: {
workers: {
miniflare: { ... },
wrangler: { configPath: "./wrangler.toml" },
},
},
},
});

Update your TypeScript configuration file

If you are using TypeScript, update your tsconfig.json to include the correct ambient types:

{
"compilerOptions": {
...,
"types": [
"@cloudflare/workers-types/experimental"
"vitest-environment-miniflare/globals"
"@cloudflare/vitest-pool-workers"
]
},
}

Access bindings

To access bindings in your tests, use the env helper from the cloudflare:test module.

import { it } from "vitest";
import { env } from "cloudflare:test";
it("does something", () => {
const env = getMiniflareBindings();
// ...
});

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:

declare module "cloudflare:test" {
interface ProvidedEnv {
NAMESPACE: KVNamespace;
}
// ...or if you have an existing `Env` type...
interface ProvidedEnv extends Env {}
}

Use isolated storage

Isolated storage is now enabled by default. You no longer need to include setupMiniflareIsolatedStorage() in your tests.

const describe = setupMiniflareIsolatedStorage();
import { describe } from "vitest";

Work with waitUntil()

The new ExecutionContext() constructor and getMiniflareWaitUntil() function are now createExecutionContext() and waitOnExecutionContext() respectively. Note waitOnExecutionContext() now returns an empty Promise<void> instead of a Promise resolving to the results of all waitUntil()ed Promises.

import { createExecutionContext, waitOnExecutionContext } from "cloudflare:test";
it("does something", () => {
// ...
const ctx = new ExecutionContext();
const ctx = createExecutionContext();
const response = worker.fetch(request, env, ctx);
await getMiniflareWaitUntil(ctx);
await waitOnExecutionContext(ctx);
});

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.
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());

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. Refer to the Test APIs page for more details.

import { env, runInDurableObject } from "cloudflare:test";
it("does something", async () => {
const env = getMiniflareBindings();
const id = env.OBJECT.newUniqueId();
const stub = env.OBJECT.get(id);
const storage = await getMiniflareDurableObjectStorage(id);
doSomethingWith(storage);
await runInDurableObject(stub, async (instance, state) => {
doSomethingWith(state.storage);
});
const state = await getMiniflareDurableObjectState(id);
doSomethingWith(state);
await runInDurableObject(stub, async (instance, state) => {
doSomethingWith(state);
});
const instance = await getMiniflareDurableObjectInstance(id);
await runWithMiniflareDurableObjectGates(state, async () => {
doSomethingWith(instance);
});
await runInDurableObject(stub, async (instance) => {
doSomethingWith(instance);
});
});

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.

import { env, runDurableObjectAlarm } from "cloudflare:test";
it("does something", async () => {
const env = getMiniflareBindings();
const id = env.OBJECT.newUniqueId();
await flushMiniflareDurableObjectAlarms([id]);
const stub = env.OBJECT.get(id);
const ran = await runDurableObjectAlarm(stub);
});

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.

import { env, listDurableObjectIds } from "cloudflare:test";
it("does something", async () => {
const ids = await getMiniflareDurableObjectIds("OBJECT");
const ids = await listDurableObjectIds(env.OBJECT);
});