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

Get started with zuplo test

This guide takes a Zuplo project from no tests to a green test run against a local dev server, in three steps. It covers where test files live, what TestHelper gives you, and the CLI flags worth knowing. The only install is one assertion library.

Every test zuplo test runs is an integration test: it makes a real HTTP request to a real running gateway and asserts on the real response. The endpoint is a command-line flag, so the same files run against local dev, a preview deployment, or production.

  1. Install an assertion library

    describe, it, the lifecycle hooks, and TestHelper come from @zuplo/test, which arrives transitively with the zuplo package. The CLI requires Node.js 24.0.0 or later, which every sample here assumes.

    Assertions in these docs use expect from chai. Chai is not included in a new Zuplo project, so install it yourself:

    TerminalCode
    npm install --save-dev chai @types/chai

    The CLI marks chai as external when it compiles your tests, so it resolves from node_modules at run time. Skip the install step and the compiler fails with Cannot find package 'chai' before any test runs.

    Prefer no dependencies? Node's built-in node:assert/strict works instead and needs nothing installed. The strict form compares with ===, so assert.equal(200, "200") fails instead of quietly passing.

  2. Write the first test

    Test files go in a tests folder at the root of your project and must end in .test.ts. Nested folders are fine.

    Code
    import { describe, it, TestHelper } from "@zuplo/test"; import { expect } from "chai"; describe("gateway", () => { it("serves the root route", async () => { const response = await fetch(TestHelper.TEST_URL); expect(response.status).to.equal(200); }); });

    TestHelper.TEST_URL is whatever you passed to --endpoint. Build request URLs from it rather than hard-coding a host — that is the single change that lets one suite serve every environment:

    Code
    const url = (path: string) => new URL(path, TestHelper.TEST_URL).toString(); const response = await fetch(url("/v1/orders"));
  3. Run it

    Start the dev server in one terminal:

    TerminalCode
    npx zuplo dev

    Run the suite in another:

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

    The CLI discovers every tests/**/*.test.ts file, compiles them into .zuplo/__tests__, and runs them. .zuplo is generated output — leave it out of source control.

    Add the endpoint you use most to a script so the common case is one word:

    Code
    { "scripts": { "test": "zuplo test --endpoint http://localhost:9000" } }

TestHelper reference

TestHelper has two static members.

MemberTypeDescription
TestHelper.TEST_URLstringThe value passed to --endpoint. Throws if zuplo test was not given one.
TestHelper.environmentRecord<string, string>The test process environment. This is process.env — the two are interchangeable, including anything from .env.

TestHelper.environment reads the environment of the test process, not the environment variables configured on your Zuplo project. Those belong to the gateway; these belong to the test process. Fixture tokens, API keys, and seed data come in this way:

TerminalCode
TENANT_A_JWT=eyJhbGciOi... npx zuplo test --endpoint http://localhost:9000
Code
import { describe, it, TestHelper } from "@zuplo/test"; import { expect } from "chai"; describe("auth", () => { it("accepts a valid token", async () => { const response = await fetch(`${TestHelper.TEST_URL}/v1/orders`, { headers: { Authorization: `Bearer ${TestHelper.environment.TENANT_A_JWT}`, }, }); expect(response.status).to.equal(200); }); });

The CLI also loads a .env file from the directory you run it in, so local fixture values can live there instead of on the command line.

Never commit fixture credentials. Keep them in .env (gitignored) locally and in your CI provider's secret store in the pipeline.

Select which tests run

Two flags select tests by name. Both match against the full test name, which is the describe label and the it label joined together, so a test named smoke: orders route answers is selected by --filter smoke even when its enclosing describe does not match.

TerminalCode
# Only tests whose name contains "auth" npx zuplo test --endpoint http://localhost:9000 --filter "auth" # Regex form — wrap the pattern in slashes npx zuplo test --endpoint http://localhost:9000 --filter "/#label[Aa]/" # Everything except tests tagged [slow] in their name npx zuplo test --endpoint http://localhost:9000 --skip-filter "\[slow\]"

--skip-filter is applied after --filter, so the two compose. This is the mechanism behind production smoke checks: name the read-only subset consistently, then run only that subset against production.

Skip a test in code

Prefix a suite or test with .skip (or its alias .ignore) to declare it without running it:

Code
import { describe, it } from "@zuplo/test"; import { expect } from "chai"; describe("arithmetic", () => { it.skip("this test is declared but not run", () => { expect(1 + 4).to.equal(6); }); it("this test runs", () => { expect(1 + 4).to.equal(5); }); });

.only takes effect only when the run is started with --only. Without the flag, zuplo test runs the marked test and every other test — silently the opposite of what you wanted, and it looks like it worked.

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

With the flag, only the marked tests and suites execute. If your CLI does not recognize --only, upgrade the zuplo package — or use --filter to narrow the run by test name, which works on every version.

Next steps

  • Gateway test recipes — what to assert at a gateway, as copy-pasteable files
  • Testing preview environments — run the same suite against the real deployment of your branch
  • Testing GitHub deployments — make a failing gateway test block the merge
  • Testing overview — when to run what, and why
Edit this page
Last modified on August 21, 2026
TestingTest recipes
On this page
  • TestHelper reference
  • Select which tests run
  • Skip a test in code
  • Next steps
TypeScript
TypeScript
JSON
TypeScript
TypeScript