cf-bun-mocks 0.1.0-alpha.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,12 +26,12 @@ const db = new D1Mock("./test.db");
26
26
 
27
27
  ### With Migrations
28
28
 
29
- Use `initD1` to create an in-memory database and run your migrations:
29
+ Use `createD1Mock` to create an in-memory database and run your migrations:
30
30
 
31
31
  ```typescript
32
- import { initD1 } from "cf-bun-mocks";
32
+ import { createD1Mock } from "cf-bun-mocks";
33
33
 
34
- const db = await initD1("./migrations");
34
+ const db = await createD1Mock("./migrations");
35
35
  ```
36
36
 
37
37
  This reads all `.sql` files from the migrations directory in sorted order and executes them.
@@ -107,30 +107,79 @@ const env: Env = {
107
107
  const response = await worker.fetch(request, env);
108
108
  ```
109
109
 
110
- ## Environment Mock
110
+ ## Workers Environment
111
111
 
112
- Use `useEnv` to mock `cloudflare:env` imports in your tests:
112
+ For testing Cloudflare Workers, use the `useWorkersEnv` helper to create test environments:
113
113
 
114
114
  ```typescript
115
115
  import { describe, test, expect } from "bun:test";
116
- import { useEnv, D1Mock } from "cf-bun-mocks";
116
+ import { useWorkersEnv, D1Mock } from "cf-bun-mocks";
117
117
  import type { Env } from "./worker";
118
118
 
119
119
  describe("my worker", () => {
120
- useEnv<Env>(async () => ({
120
+ const { env } = useWorkersEnv<Env>(() => ({
121
121
  DB: new D1Mock(":memory:"),
122
122
  MY_SECRET: "test-secret",
123
123
  }));
124
124
 
125
- test("uses mocked env", async () => {
126
- // Your worker code that imports from "cloudflare:env" will get the mock
127
- const { myFunction } = await import("./worker");
128
- const result = await myFunction();
129
- expect(result).toBeDefined();
125
+ test("uses test environment", async () => {
126
+ // Pass the env to your worker handler
127
+ const response = await worker.fetch(request, env);
128
+ expect(response.status).toBe(200);
130
129
  });
131
130
  });
132
131
  ```
133
132
 
133
+ ### Module Mocking (Advanced)
134
+
135
+ For mocking `cloudflare:workers` module imports, use `setupWorkersMock` in a preload script and combine it with `useWorkersEnv`:
136
+
137
+ Create a `test-setup.ts` file:
138
+
139
+ ```typescript
140
+ // test-setup.ts
141
+ import { setupWorkersMock } from "cf-bun-mocks";
142
+
143
+ // Set up module mocks before any test files load
144
+ await setupWorkersMock();
145
+ ```
146
+
147
+ Then run tests with preload:
148
+
149
+ ```bash
150
+ bun test --preload ./test-setup.ts
151
+ ```
152
+
153
+ Or configure it in `bunfig.toml`:
154
+
155
+ ```toml
156
+ [test]
157
+ preload = ["./test-setup.ts"]
158
+ ```
159
+
160
+ Then use in your tests:
161
+
162
+ ```typescript
163
+ import { describe, test, expect } from "bun:test";
164
+ import { useWorkersEnv, D1Mock } from "cf-bun-mocks";
165
+
166
+ describe("worker with module imports", () => {
167
+ useWorkersEnv(() => ({
168
+ DB: new D1Mock(":memory:"),
169
+ API_KEY: "test-key",
170
+ }));
171
+
172
+ test("uses mocked module", async () => {
173
+ // The env object reference stays the same, only properties change
174
+ const { env } = await import("cloudflare:workers");
175
+ expect(env.DB).toBeDefined();
176
+ expect(env.API_KEY).toBe("test-key");
177
+ });
178
+ });
179
+ ```
180
+
181
+ > **Note**: `setupWorkersMock` uses Bun's `mock.module()` function. Module mocks must be set up before any imports happen. See [Bun's test lifecycle docs](https://bun.com/docs/test/lifecycle#global-setup-and-teardown) for preload details and [Bun's mocking docs](https://bun.sh/docs/test/mocking) for more on module mocking.
182
+
134
183
  ## API
135
184
 
136
185
  ### `D1Mock`
@@ -139,17 +188,21 @@ Implements the full `D1Database` interface from `@cloudflare/workers-types`:
139
188
 
140
189
  - `prepare(query: string)` - Create a prepared statement
141
190
  - `batch(statements: D1PreparedStatement[])` - Execute multiple statements
142
- - `exec(query: string)` - Execute raw SQL
191
+ - `exec(query: string)` - Execute raw SQL (supports multiple statements)
143
192
  - `dump()` - Serialize the database to an ArrayBuffer
144
193
  - `withSession(constraint?)` - Get a session (bookmark tracking is stubbed)
145
194
 
146
- ### `initD1(migrationsPath: string)`
195
+ ### `createD1Mock(migrationsPath: string)`
196
+
197
+ Creates an in-memory D1Mock and runs all `.sql` migration files from the specified directory in sorted order.
198
+
199
+ ### `useWorkersEnv<TEnv>(createEnv: () => Partial<TEnv> | Promise<Partial<TEnv>>)`
147
200
 
148
- Creates an in-memory D1Mock and runs all `.sql` files from the specified directory.
201
+ Updates the global workers mock environment for each test. Must be used with `setupWorkersMock()`.
149
202
 
150
- ### `useEnv<TEnv>(setup: () => TEnv | Promise<TEnv>)`
203
+ ### `setupWorkersMock<TEnv>(createMock?: () => WorkersModuleMock<TEnv> | Promise<WorkersModuleMock<TEnv>>)`
151
204
 
152
- Registers `beforeEach`/`afterEach` hooks to mock `cloudflare:env` for each test. Call at the top of your `describe` block.
205
+ Sets up the `cloudflare:workers` module mock using Bun's `mock.module()`. **Must be called in a preload script before any test files load.** Use Bun's `--preload` flag or configure it in `bunfig.toml`. Call once at the start of your test suite, then use `useWorkersEnv()` to update the environment per test.
153
206
 
154
207
  ## Requirements
155
208
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-bun-mocks",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.2.0",
4
4
  "description": "Cloudflare Workers mocks and helpers for Bun testing",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -15,9 +15,9 @@
15
15
  "import": "./src/d1.ts",
16
16
  "types": "./src/d1.ts"
17
17
  },
18
- "./env": {
19
- "import": "./src/env.ts",
20
- "types": "./src/env.ts"
18
+ "./workers": {
19
+ "import": "./src/workers.ts",
20
+ "types": "./src/workers.ts"
21
21
  }
22
22
  },
23
23
  "files": [
package/src/d1.ts CHANGED
@@ -147,12 +147,20 @@ export class D1Mock implements D1Database {
147
147
 
148
148
  async exec(query: string): Promise<D1ExecResult> {
149
149
  const start = performance.now();
150
- const { changes: count } = this.#db.run(query);
151
- const duration = performance.now() - start;
152
- return {
153
- count,
154
- duration,
155
- };
150
+ try {
151
+ const { changes: count } = this.#db.run(query);
152
+ const duration = performance.now() - start;
153
+ return {
154
+ count,
155
+ duration,
156
+ };
157
+ } catch (error) {
158
+ throw new Error(
159
+ `D1Mock exec failed: ${
160
+ error instanceof Error ? error.message : String(error)
161
+ }\nQuery: ${query}`
162
+ );
163
+ }
156
164
  }
157
165
 
158
166
  withSession(
@@ -173,14 +181,22 @@ export class D1Mock implements D1Database {
173
181
  }
174
182
  }
175
183
 
176
- export async function initD1(migrationsPath: string): Promise<D1Mock> {
184
+ export async function createD1Mock(migrationsPath: string): Promise<D1Mock> {
177
185
  const files = readdirSync(migrationsPath)
178
186
  .filter((file) => file.endsWith(".sql"))
179
187
  .sort();
180
188
  const db = new D1Mock(":memory:");
181
189
  for (const file of files) {
182
190
  const migration = await Bun.file(path.join(migrationsPath, file)).text();
183
- await db.exec(migration);
191
+ try {
192
+ await db.exec(migration);
193
+ } catch (error) {
194
+ throw new Error(
195
+ `Failed to execute migration ${file}: ${
196
+ error instanceof Error ? error.message : String(error)
197
+ }`
198
+ );
199
+ }
184
200
  }
185
201
  return db;
186
202
  }
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export * from "./d1.ts";
2
- export * from "./env.ts";
2
+ export * from "./workers.ts";
package/src/workers.ts ADDED
@@ -0,0 +1,33 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import { beforeEach, mock, afterAll } from "bun:test";
3
+
4
+ type WorkersModuleMock<TEnv extends Cloudflare.Env = Cloudflare.Env> = {
5
+ env: Partial<TEnv>;
6
+ };
7
+
8
+ let moduleMock: WorkersModuleMock = { env: {} };
9
+
10
+ export async function setupWorkersMock<
11
+ TEnv extends Cloudflare.Env = Cloudflare.Env
12
+ >(createMock?: () => Bun.MaybePromise<WorkersModuleMock<TEnv>>) {
13
+ if (createMock) {
14
+ moduleMock = await createMock();
15
+ }
16
+ mock.module("cloudflare:workers", () => moduleMock);
17
+ }
18
+
19
+ export function useWorkersEnv<TEnv extends Cloudflare.Env = Cloudflare.Env>(
20
+ createEnv: () => Bun.MaybePromise<Partial<TEnv>>
21
+ ) {
22
+ beforeEach(async () => {
23
+ const env = await createEnv();
24
+ for (const key in moduleMock.env) {
25
+ delete (moduleMock.env as any)[key];
26
+ }
27
+ Object.assign(moduleMock.env, env);
28
+ });
29
+
30
+ afterAll(() => {
31
+ moduleMock.env = {};
32
+ });
33
+ }
package/src/env.ts DELETED
@@ -1,17 +0,0 @@
1
- /// <reference types="@cloudflare/workers-types" />
2
- import { beforeEach, afterEach, mock } from "bun:test";
3
-
4
- const MODULE_NAME = "cloudflare:workers";
5
-
6
- export function useEnv<TEnv extends Cloudflare.Env = Cloudflare.Env>(
7
- setup: () => Bun.MaybePromise<Partial<TEnv>>
8
- ) {
9
- beforeEach(async () => {
10
- const env = await setup();
11
- mock.module(MODULE_NAME, () => env);
12
- });
13
-
14
- afterEach(() => {
15
- mock.module(MODULE_NAME, () => {});
16
- });
17
- }