ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Getting Started
    Develop in the portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
    Develop locally with the CLI
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
Concepts
API Management
AI Gateway
MCP Gateway
MCP Server
Developer Portal
Development
    CORSEnvironment VariablesBranch-Based DeploymentsTroubleshootingGitOps vs TerraformCustom Code
    Testing
      Get startedTest recipesPreview environmentsTest deployments
    Local Development
    Guides
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsVersion Support PolicySecuritySupportTrust & ComplianceChangelog
powered by Zudoku

Testing Your API

Your gateway holds authentication, authorization, rate limits, routing, request validation, and the shape of every error your consumers see. All of that is configuration, and configuration is exactly what unit tests cannot reach. A unit test can prove a JWT validation function rejects an expired token, but it cannot prove the policy that calls that function is attached to the route your customers hit.

zuplo test closes that gap. It runs plain TypeScript test files from your repository against a live gateway, and it takes the target as a flag. The same files run against your local dev server, against the preview deployment of your branch, and against production.

TerminalCode
npx zuplo test --endpoint http://localhost:9000 npx zuplo test --endpoint https://your-branch-abc123.zuplo.app npx zuplo test --endpoint https://api.example.com --filter "smoke"

New to zuplo test? Start with Get started with zuplo test, which takes a fresh project to a green run.

Same assertions, rising fidelity

Structure the suite as one set of assertions run at rising levels of fidelity. Maintaining different tests per stage means the stage you trust least gets the tests nobody maintains.

StageWhen to useEndpoint target
Local devFast feedback while developinghttp://localhost:9000
Preview environmentValidate a change on a real deployment before merginghttps://<branch>-<id>.zuplo.app
CI/CD gateBlock a merge when gateway behavior regressesDeployment URL from your CI provider
Production smoke checksA small, read-only subset after deploy and on a scheduleYour production URL

All of them use the same test files and the same command. The only thing that changes is --endpoint — and, for production, a --filter that narrows the run to the read-only subset.

Nothing with side effects belongs in the production subset. Name those tests consistently (a smoke: prefix works well) and select them by name.

What to test

The interesting assertions at a gateway are the rejections, because the happy path hides the worst defects. A policy attached to the wrong route does not fail — it succeeds for the wrong caller.

Gateway test recipes has a complete, copy-pasteable file for each of these:

  • Auth rejections — missing token, expired token, wrong audience, and a valid token from one tenant asking for another tenant's object
  • Rate limits, deterministically — a fresh bucket per test, a sequential loop, and an assertion on 429 plus Retry-After
  • OpenAPI conformance — validate live responses against the routes.oas.json that defines the routes
  • The error contract — application/problem+json and the RFC 9457 members
  • Routing and CORS — unknown paths, legacy aliases, preflight responses
  • Test data hygiene — keep a parallel suite from corrupting itself

Local testing

Running against a local development server gives the fastest feedback loop. Start the server with zuplo dev:

TerminalCode
npx zuplo dev

The gateway starts on http://localhost:9000 by default. In a second terminal:

TerminalCode
npx zuplo test --endpoint http://localhost:9000

Test with Zuplo services locally

Some features, such as API key authentication and rate limiting, require a connection to Zuplo cloud services. To exercise those policies in local development, link your local project to an existing Zuplo project with zuplo link:

TerminalCode
npx zuplo link

Follow the prompts to select your account, project, and environment. This creates a .env.zuplo file that the dev server reads automatically. We recommend selecting the development environment for local development.

The .env.zuplo file can contain sensitive information. Add it to your .gitignore file so it is not committed to source control.

Once linked, the API Key Authentication policy works locally using the same API key bucket as the linked environment. In the Zuplo Portal, open Services in your project and select API Key Service to create API key consumers. Then call your local gateway with the generated key:

TerminalCode
curl http://localhost:9000/your-route \ -H "Authorization: Bearer YOUR_API_KEY"

For more details, see connecting to Zuplo services locally.

Set environment variables locally

Your local dev server does not have access to the environment variables configured in the Zuplo Portal. Create a .env file in your project root instead:

Code
MY_BACKEND_URL=https://api.example.com MY_SECRET=supersecret

The Zuplo CLI loads these variables automatically when you run npx zuplo dev, and the same file supplies fixture values to zuplo test. See configuring environment variables locally.

Test before merge

Every branch pushed to your connected source control provider deploys as a preview environment: a full Zuplo deployment on the same edge network as production, at its own URL. Running your suite there is the highest-fidelity check available before a change merges, and it needs no shared staging environment.

  • Testing preview environments — getting the URL into your tests, per-environment services and secrets, and what to scope where
  • Testing GitHub deployments — trigger on the deployment_status event and make the workflow a required status check
  • Custom CI/CD — the same pattern for GitLab, Bitbucket, Azure DevOps, and CircleCI

Poll for readiness, never sleep

Most deployment APIs report success when a deployment is accepted, not when the gateway is serving traffic. Tests fired into that gap fail, get retried, and the pipeline race gets misfiled as flaky tests. Poll a health route with a bounded deadline before running the suite — the CI guides above all show the loop.

Unit tests and mocking

zuplo test suites and the gateway are separate processes that communicate over HTTP. A test can mock anything in its own process — a helper that wraps fetch, a clock, a fixture module — but nothing inside the gateway. To control what the gateway sees, change the gateway instead: return canned responses with the Mock API policy, disable a policy for test traffic with the Testing bypass policy, or point the route at your own stub server.

Advanced

Custom testing can be complicated. Use it to test your own logic rather than trying to mock large portions of your gateway.

You can use test frameworks like Mocha and mocking tools like Sinon to unit test handlers, policies, or other modules. For an example, see this sample on GitHub.

Not everything in the Zuplo runtime can be mocked, and internal implementation changes might cause mocking behavior to change or break without notice. Unlike the public API, mocking is not guaranteed to remain stable between versions.

If you must write unit tests, test your logic separately from the Zuplo runtime. Write modules and functions that take all their arguments as input and return a result, without depending on Zuplo runtime code.

For example, if you have a function that uses an environment variable and want to unit test it, don't do this:

Code
import { environment } from "@zuplo/runtime"; export function myFunction() { const myVar = environment.MY_ENV_VAR; return `Hello ${myVar}`; }

Do this instead:

Code
export function myFunction(myVar: string) { return `Hello ${myVar}`; }

Then write your test:

Code
import { myFunction } from "./myFunction"; describe("myFunction", () => { it("returns Hello World", () => { expect(myFunction("World")).to.equal("Hello World"); }); });

Polyfills

If you are running unit tests in a Node.js environment, you may need to polyfill some globals. Zuplo itself doesn't run on Node.js, but because Zuplo is built on standard APIs, testing in Node.js is possible.

Node.js 24, the minimum version the Zuplo CLI supports, includes the webcrypto module, which polyfills the crypto global. You must register this polyfill before any Zuplo code runs.

Code
import { webcrypto } from "node:crypto"; if (typeof crypto === "undefined") { globalThis.crypto = webcrypto; }

Related

  • Get started with zuplo test
  • Gateway test recipes
  • Testing preview environments
  • Testing GitHub deployments
  • zuplo test CLI reference
Edit this page
Last modified on August 21, 2026
Custom CodeGet started
On this page
  • Same assertions, rising fidelity
  • What to test
  • Local testing
    • Test with Zuplo services locally
    • Set environment variables locally
  • Test before merge
  • Unit tests and mocking
    • Polyfills
  • Related
TypeScript
TypeScript
TypeScript
Javascript