> ## Documentation Index
> Fetch the complete documentation index at: https://wemstudios.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing Blaze Workers with Vitest and Mock Environments

> Test Blaze Workers without a real Cloudflare runtime. Pass synthetic Request objects and a mock Env to app.fetch() and assert on the Response.

`app.fetch(req, env, ctx)` is your single test seam. Pass a synthetic `Request`, a plain mock `env` object, and a mock `ExecutionContext` — no Worker runtime, no `wrangler dev`, no live Cloudflare account needed. Because Blaze enriches the standard `Request` internally, your tests stay clean and fast.

## Unit testing with Vitest

Vitest runs in a standard Node.js environment. Build up a `mockEnv` with `vi.fn()` stubs for each binding your handler touches, then call `app.fetch()` and assert on the `Response`.

<Steps>
  ### Install Vitest

  ```bash theme={null}
  npm install --save-dev vitest
  ```

  ### Configure Vitest

  ```ts vitest.config.ts theme={null}
  import { defineConfig } from 'vitest/config';

  export default defineConfig({
    test: {
      environment: 'node',
    },
  });
  ```

  ### Write your first test

  ```ts test/users.test.ts theme={null}
  import { describe, it, expect, vi } from 'vitest';
  import app from '../src/index';

  // Build a mock env that matches your Env type
  const mockEnv = {
    DB: {
      prepare: vi.fn().mockReturnValue({
        bind: vi.fn().mockReturnValue({
          first: vi.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
          all:   vi.fn().mockResolvedValue({ results: [] }),
          run:   vi.fn().mockResolvedValue({ success: true }),
        }),
      }),
    },
    SESSION_KV: {
      get: vi.fn().mockResolvedValue(null),
      put: vi.fn().mockResolvedValue(undefined),
    },
  };

  const mockCtx = {
    waitUntil:             vi.fn(),
    passThroughOnException: vi.fn(),
  };

  describe('GET /users/:id', () => {
    it('returns 200 with user data', async () => {
      const req = new Request('http://localhost/users/1');
      const res = await app.fetch(req, mockEnv, mockCtx);

      expect(res.status).toBe(200);

      const body = await res.json();
      expect(body).toMatchObject({ id: '1', name: 'Alice' });
    });

    it('returns 404 when user not found', async () => {
      // Override just the first() call for this test
      mockEnv.DB.prepare.mockReturnValueOnce({
        bind: () => ({
          first: () => Promise.resolve(null),
        }),
      });

      const req = new Request('http://localhost/users/999');
      const res = await app.fetch(req, mockEnv, mockCtx);

      expect(res.status).toBe(404);

      const body = await res.json();
      expect(body).toMatchObject({ error: 'Not found' });
    });
  });
  ```
</Steps>

<Tip>
  Use `mockCtx.waitUntil` as a `vi.fn()` to assert on background tasks. After calling `app.fetch()`, inspect `mockCtx.waitUntil.mock.calls` to verify that analytics writes, cache warming, or other fire-and-forget work was scheduled — without actually running the deferred work.
</Tip>

## Integration testing with @cloudflare/vitest-pool-workers

For tests that need real Cloudflare APIs — Workers KV, D1, R2 — use the `@cloudflare/vitest-pool-workers` pool. It runs your tests inside an actual Workers runtime via Miniflare, so bindings behave exactly as they do in production.

<Steps>
  ### Install the pool package

  ```bash theme={null}
  npm install --save-dev @cloudflare/vitest-pool-workers
  ```

  ### Configure Vitest for the pool

  ```ts vitest.config.ts theme={null}
  import { defineConfig } from 'vitest/config';

  export default defineConfig({
    test: {
      pool: '@cloudflare/vitest-pool-workers',
      poolOptions: {
        workers: {
          wrangler: { configPath: './wrangler.toml' },
        },
      },
    },
  });
  ```

  ### Write an integration test using SELF

  ```ts test/workers.test.ts theme={null}
  import { it, expect } from 'vitest';
  import { SELF } from 'cloudflare:test';

  it('reads a real KV value', async () => {
    const res = await SELF.fetch('http://example.com/items/hello');
    expect(res.status).toBe(200);

    const body = await res.json();
    expect(body).toHaveProperty('value');
  });

  it('inserts into D1 and reads back', async () => {
    // POST creates a user
    const createRes = await SELF.fetch('http://example.com/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: 'Bob', email: 'bob@example.com' }),
    });
    expect(createRes.status).toBe(201);
    const { id } = await createRes.json();

    // GET reads the user back
    const getRes = await SELF.fetch(`http://example.com/users/${id}`);
    expect(getRes.status).toBe(200);
    const user = await getRes.json();
    expect(user.name).toBe('Bob');
  });
  ```
</Steps>

## Mocking Cloudflare bindings

Different bindings need different mock shapes. Here are the patterns for the most common ones:

<Tabs>
  <Tab title="KV">
    ```ts theme={null}
    const mockKV: Partial<KVNamespace> = {
      get: vi.fn().mockImplementation(async (key: string) => {
        const store: Record<string, string> = {
          'user:1': JSON.stringify({ id: '1', name: 'Alice' }),
        };
        return store[key] ?? null;
      }),
      put: vi.fn().mockResolvedValue(undefined),
      delete: vi.fn().mockResolvedValue(undefined),
      list: vi.fn().mockResolvedValue({ keys: [], list_complete: true, cursor: '' }),
    };

    const mockEnv = { SESSION_KV: mockKV };
    ```
  </Tab>

  <Tab title="D1">
    ```ts theme={null}
    // Mock the prepare → bind → first/all/run chain
    const mockD1: Partial<D1Database> = {
      prepare: vi.fn().mockReturnValue({
        bind: vi.fn().mockReturnValue({
          first:  vi.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
          all:    vi.fn().mockResolvedValue({ results: [{ id: '1' }], success: true }),
          run:    vi.fn().mockResolvedValue({ success: true, meta: {} }),
        }),
      }),
      batch: vi.fn().mockResolvedValue([{ results: [], success: true }]),
    };

    const mockEnv = { DB: mockD1 };
    ```
  </Tab>

  <Tab title="R2">
    ```ts theme={null}
    const mockR2: Partial<R2Bucket> = {
      get: vi.fn().mockImplementation(async (key: string) => {
        if (key === 'exists.txt') {
          return {
            key,
            arrayBuffer: () => Promise.resolve(new ArrayBuffer(4)),
            httpMetadata: { contentType: 'text/plain' },
            httpEtag: '"abc123"',
          };
        }
        return null; // 404 path
      }),
      put: vi.fn().mockResolvedValue({ key: 'new-file.txt', etag: '"def456"' }),
      delete: vi.fn().mockResolvedValue(undefined),
    };

    const mockEnv = { ASSETS: mockR2 };
    ```
  </Tab>

  <Tab title="Queue">
    ```ts theme={null}
    const mockQueue: Partial<Queue> = {
      send:      vi.fn().mockResolvedValue(undefined),
      sendBatch: vi.fn().mockResolvedValue(undefined),
    };

    const mockEnv = { JOBS: mockQueue };

    // Assert a message was enqueued
    it('enqueues a job on POST /jobs', async () => {
      const req = new Request('http://localhost/jobs', {
        method: 'POST',
        body: JSON.stringify({ type: 'email', to: 'user@example.com' }),
        headers: { 'Content-Type': 'application/json' },
      });

      const res = await app.fetch(req, mockEnv, mockCtx);
      expect(res.status).toBe(202);
      expect(mockQueue.send).toHaveBeenCalledWith({
        type: 'email',
        to: 'user@example.com',
      });
    });
    ```
  </Tab>
</Tabs>

## Testing middleware

Test auth middleware by controlling the `Authorization` header. Pass the header to simulate an authenticated request; omit it (or supply an invalid token) to test the rejection path.

```ts test/auth.test.ts theme={null}
import { describe, it, expect, vi } from 'vitest';
import app from '../src/index';

const mockEnv = {
  JWT_SECRET: 'test-secret',
  DB: { /* ... */ },
};

const mockCtx = { waitUntil: vi.fn(), passThroughOnException: vi.fn() };

describe('requireAuth middleware', () => {
  it('allows requests with a valid Bearer token', async () => {
    const req = new Request('http://localhost/api/profile', {
      headers: { Authorization: 'Bearer valid-token-for-testing' },
    });

    const res = await app.fetch(req, mockEnv, mockCtx);
    // Expect the actual route response, not a 401
    expect(res.status).not.toBe(401);
  });

  it('rejects requests with no Authorization header', async () => {
    const req = new Request('http://localhost/api/profile');
    const res = await app.fetch(req, mockEnv, mockCtx);

    expect(res.status).toBe(401);
    const body = await res.json();
    expect(body).toMatchObject({ error: 'Unauthorized' });
  });

  it('rejects requests with a malformed token', async () => {
    const req = new Request('http://localhost/api/profile', {
      headers: { Authorization: 'Bearer bad.token.here' },
    });

    const res = await app.fetch(req, mockEnv, mockCtx);
    expect(res.status).toBe(401);
  });
});
```

## Testing error handlers

Assert on the status code *and* response body to verify your error handler shapes the response correctly, including custom metadata from `BlazeError`.

```ts test/errors.test.ts theme={null}
import { describe, it, expect, vi } from 'vitest';
import app from '../src/index';

const mockCtx = { waitUntil: vi.fn(), passThroughOnException: vi.fn() };

describe('Error handling', () => {
  it('returns 500 for unexpected errors', async () => {
    const req = new Request('http://localhost/error');
    const res = await app.fetch(req, {}, mockCtx);

    expect(res.status).toBe(500);
    const body = await res.json();
    expect(body).toMatchObject({ error: 'Internal Server Error' });
  });

  it('returns structured BlazeError responses', async () => {
    const req = new Request('http://localhost/admin/secret');
    const res = await app.fetch(req, {}, mockCtx);

    expect(res.status).toBe(403);
    const body = await res.json();
    // BlazeError metadata is spread into the response body
    expect(body).toMatchObject({
      error: 'Forbidden',
      code:  'INSUFFICIENT_PERMISSIONS',
    });
  });

  it('returns 404 for unknown routes', async () => {
    const req = new Request('http://localhost/does-not-exist');
    const res = await app.fetch(req, {}, mockCtx);

    expect(res.status).toBe(404);
    const body = await res.json();
    expect(body).toMatchObject({ error: 'Not Found' });
  });
});
```

<Tip>
  To test that background tasks are scheduled correctly, assert on `mockCtx.waitUntil.mock.calls` after `await app.fetch(...)`. For example, if your analytics handler writes a data point after every `GET /products/:id`, you can verify `expect(mockCtx.waitUntil).toHaveBeenCalledOnce()` without waiting for the deferred promise to settle.
</Tip>
