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.
Code
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.
| Stage | When to use | Endpoint target |
|---|---|---|
| Local dev | Fast feedback while developing | http://localhost:9000 |
| Preview environment | Validate a change on a real deployment before merging | https://<branch>-<id>.zuplo.app |
| CI/CD gate | Block a merge when gateway behavior regresses | Deployment URL from your CI provider |
| Production smoke checks | A small, read-only subset after deploy and on a schedule | Your 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
429plusRetry-After - OpenAPI conformance — validate live responses against the
routes.oas.jsonthat defines the routes - The error contract —
application/problem+jsonand 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:
Code
The gateway starts on http://localhost:9000 by default. In a second terminal:
Code
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:
Code
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:
Code
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
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_statusevent 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
Do this instead:
Code
Then write your test:
Code
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
Related
- Get started with zuplo test
- Gateway test recipes
- Testing preview environments
- Testing GitHub deployments
zuplo testCLI reference