npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bun-test-cloudflare

v0.0.7

Published

Run Cloudflare Workers tests in Bun

Readme

bun-test-cloudflare

npm version

Bun test support and a typed harness wrapper for Cloudflare Workers projects, with runtime compatibility patches for running Wrangler test servers under Bun.

What It Provides

  • bun-test-cloudflare/setup: Bun test preload that patches the runtime pieces Wrangler/Miniflare needs under Bun.
  • bun-test-cloudflare: createCloudflareHarness() wrapper that turns named worker config into typed worker handles.

The setup currently fixes Bun/Miniflare websocket compatibility by adapting bare ws imports to Bun's native websocket client while preserving npm ws server exports for Miniflare internals. It also provides a minimal cloudflare:workers DurableObject shim for plain Bun module imports.

Install

bun add -d bun-test-cloudflare

For a workspace package, use:

Wrangler Compatibility

bun-test-cloudflare requires wrangler >= 4.104.0.

Configure Bun

Preload the setup before app-specific test setup:

[test]
preload = ["bun-test-cloudflare/setup", "./src/tests/setup.ts"]

If you do not need app-specific setup, use only:

[test]
preload = ["bun-test-cloudflare/setup"]

Create A Typed Harness

Create one test harness module for your package and export the configured harness:

// src/tests/harness.ts
import { createCloudflareHarness, typeToken } from "bun-test-cloudflare";
import path from "node:path";

type BackendBindings = {
  IMAGES_BUCKET: R2Bucket;
};

const packageRoot = path.resolve(import.meta.dir, "../..");

export const harness = createCloudflareHarness({
  workers: {
    BACKEND: {
      bindings: typeToken<BackendBindings>(),
      configPath: path.join(packageRoot, "wrangler.toml"),
      name: "my-backend-worker",
      vars: {
        APP_ENV: "test",
      },
    },
    CMS: {
      configPath: path.join(packageRoot, "../cms/wrangler.toml"),
      name: "my-cms-worker",
      secrets: {
        PAYLOAD_SECRET: "test",
      },
    },
  },
});

export type TestWorkers = Parameters<Parameters<typeof harness.run>[0]>[0];

The object keys become the typed worker handles passed to run(). The optional bindings token is type-only metadata for worker.getEnv() and is not passed to Wrangler.

When a worker uses configPath, bun-test-cloudflare reads that Wrangler config, injects define["process.env.NODE_ENV"] = "'test'", and runs wrangler deploy --dry-run --outdir once for that harness. Build output is written to node_modules/.btcf/worker-build/<worker-name>/worker.js. Test runs then use that script with no_bundle = true, so Wrangler does not rerun its esbuild bundle step for every run().

Use In Tests

import { expect, test } from "bun:test";
import { harness } from "./harness";

test("calls the backend worker", async () => {
  await harness.run(async (workers) => {
    const response = await workers.BACKEND.fetch("https://example.com/health");

    expect(response.status).toBe(200);
  });
});

run() creates a fresh Wrangler test server, starts it before the callback, and always closes it afterwards, including when the callback throws.

Lifecycle Events

Use events.beforeRun for per-run setup after Wrangler has started and before the test callback runs:

const harness = createCloudflareHarness({
  events: {
    beforeRun: async (workers) => {
      const env = await workers.BACKEND.getEnv();
      await env.DB.prepare("SELECT 1").run();
    },
  },
  workers: {
    BACKEND: { configPath: "./wrangler.toml" },
  },
});

Worker Names

createCloudflareHarness() uses each worker config's name when calling Wrangler's server.getWorker(name). If name is omitted, it falls back to the object key:

const harness = createCloudflareHarness({
  workers: {
    BACKEND: { configPath: "./wrangler.toml" },
  },
});

await harness.run(async (workers) => {
  await workers.BACKEND.fetch("https://example.com");
});

Direct Server Access

The current Wrangler server is available inside run() when needed:

await harness.run(async (workers, server) => {
  const logs = server.getLogs();
  const env = await workers.BACKEND.getEnv();
  await env.IMAGES_BUCKET.put("fixture.png", new Uint8Array());
});

Access The Active Run Context

Code called inside harness.run() can read the active workers and server without threading them through every helper:

import { getCloudflareHarnessRunContext } from "bun-test-cloudflare";

export async function createFixture() {
  const { workers } = getCloudflareHarnessRunContext<{
    BACKEND: { configPath: string; name: string };
  }>();
  const env = await workers.BACKEND.getEnv();

  await env.MY_BUCKET.put("fixture.txt", "hello");
}

The run context is backed by AsyncLocalStorage, so it is scoped to the current harness.run() callback and async work started from it. Calling getCloudflareHarnessRunContext() outside harness.run() throws.