Testing

Write unit and end-to-end tests for your app, run them against a live preview deploy, and (optionally) block merges to main until they pass. Tests run inside your deployed Worker with the same env bindings as your routes.

Write tests in api/test.js

api/test.js is a special file (not an HTTP route). Export a tests object mapping a test name to a function. Each function receives { fetch, assert, env } and fails by throwing (or by a failed assert):

// api/test.js
import { total } from "./cart.js"; // your own code, for unit tests

export const tests = {
  // Unit test — call your app's functions directly.
  "total() sums line items": ({ assert }) => {
    assert(total([{ price: 200, qty: 2 }, { price: 50, qty: 1 }]) === 450);
  },

  // E2E test — hit your real routes over HTTP on the preview deploy.
  "home page loads": async ({ fetch, assert }) => {
    const res = await fetch("/");
    assert(res.status === 200, `expected 200, got ${res.status}`);
  },

  "api/echo round-trips JSON": async ({ fetch, assert }) => {
    const res = await fetch("/api/echo", {
      method: "POST",
      body: JSON.stringify({ hi: 1 }),
    });
    const json = await res.json();
    assert(json.hi === 1, "echo mismatch");
  },
};
  • Unit tests import and call your own modules directly — fast, no network.
  • E2E tests use fetch(path) to make real HTTP requests to your app’s own routes on the running preview.
  • assert(condition, message?) throws when the condition is falsy; any thrown error marks the test failed and records its message.
  • Tests run with your app’s env (e.g. env.DB, env.STORAGE) — on a preview deploy these point at the isolated preview database and bucket, never production data.

Run them

Tests run against a branch’s preview deploy, so deploy the branch first, then run:

  • Deploy the branch to a preview (deploy_app).
  • Run run_tests (MCP) or the Run tests button on the app’s dashboard page. It returns { status, total, passed, failed, results }.

The run is recorded against the exact commit that was on the preview, so a green result only counts for that commit — commit again and you re-run.

The merge-to-main gate

If your app has an api/test.js, a pull request into main will only merge when the source branch’s latest commit has a passing test run. So the loop is:

edit  →  deploy_app (preview)  →  run_tests (until green)  →  merge_pull_request
An org owner or admin can bypass a red or missing gate with merge_pull_request(force: true) — use it only when you knowingly accept failing or absent tests. Apps with no api/test.js merge exactly as before; testing is opt-in per app.

Notes

  • Test source in api/test.js is never served publicly — like api/cron.js, it’s a reserved module, not a route.
  • Testing is available on every plan, including Free. See Branches, PRs & AI merges for the surrounding workflow.