Skip to content

Modify and passthrough ​

Use continue(), fallback(), and fetch() when a request should reach the real upstream service, with or without changes.

Choose the right method ​

MethodTerminalUse it when
route.continue(options?)YesYou want the app to receive the real upstream response as-is.
route.fallback(options?)NoYou want the next matching handler in the same test to see the request.
route.fetch(options?)NoYou want to inspect or transform the upstream response before fulfilling.

Unmatched requests also pass through automatically.

Continue upstream ​

ts
await backendMocks.route("https://api.example.test/**", async (route) => {
  await route.continue();
});

continue() is useful when you want to observe a request through waitForRequest() or proxy history while still hitting the real service.

Continue with overrides ​

ts
await backendMocks.route("https://api.example.test/users", async (route) => {
  await route.continue({
    url: "https://staging-api.example.test/users",
    method: "POST",
    headers: {
      "x-test-run": "checkout",
    },
    postData: { seeded: true },
  });
});

Continue options ​

OptionTypeDescription
urlstringOverride the request URL. The protocol must match the original URL.
methodstringOverride the HTTP method.
headersRecord<string, string | undefined>Override request headers; undefined deletes that header.
postDatastring | Buffer | Uint8Array | objectOverride the request body. Objects are JSON-stringified.

Forbidden browser-style headers such as host, cookie, content-length, connection, origin, referer, sec-*, and proxy-* are restored from the original request where applicable.

Fallback to another handler ​

fallback() applies local request overrides, then lets the next matching handler run.

ts
await backendMocks.route("https://api.example.test/users", async (route) => {
  await route.fulfill({ json: [{ id: 1, name: "Base" }] });
});

await backendMocks.route("https://api.example.test/users", async (route, request) => {
  if (request.headers()["x-use-base"] === "1") {
    await route.fallback({
      headers: {
        ...request.headers(),
        "x-seen-by-first-handler": "1",
      },
    });
    return;
  }

  await route.fulfill({ json: [{ id: 2, name: "Newest" }] });
});

HTTP routes are newest-first. In this example, the second route runs first and can fall back to the first route.

Fetch then modify ​

fetch() performs an upstream request from the handler without settling the route.

ts
await backendMocks.route("https://api.example.test/users", async (route) => {
  const upstream = await route.fetch();
  const users = (await upstream.json()) as Array<{ id: number; name: string }>;

  await route.fulfill({
    response: upstream,
    json: [...users, { id: 999, name: "Injected by test" }],
  });
});

Fetch options ​

OptionTypeDefaultDescription
urlstringoriginal URLUpstream URL for the fetch. Must be http: or https:.
methodstringoriginal methodUpstream method.
headersRecord<string, string | undefined>original headersRequest headers; undefined values are dropped.
postDatastring | Buffer | Uint8Array | objectoriginal bodyRequest body. Objects get JSON content-type defaults.
timeoutnumber30000Milliseconds before the fetch rejects. 0 disables the timeout.
maxRedirectsnumber20Maximum redirects. 0 means do not follow redirects.
maxRetriesnumber0Retries for reset-style network failures.
signalAbortSignalnoneCancels the fetch promise.

fetch() bypasses the Node interceptor so it does not re-enter your backend mock routes.

Rewriting one upstream to another ​

ts
await backendMocks.route("https://api.example.test/users", async (route) => {
  const response = await route.fetch({
    url: "https://fixtures.example.test/users",
    headers: {
      authorization: "Bearer test-token",
    },
  });

  await route.fulfill({ response });
});

Mock the outside world. Keep the real app.