zuplo 7.4.4 → 7.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/docs/articles/ci-cd-azure/local-testing.mdx +22 -4
  2. package/docs/articles/ci-cd-bitbucket/local-testing.mdx +22 -4
  3. package/docs/articles/ci-cd-circleci/local-testing.mdx +21 -4
  4. package/docs/articles/ci-cd-github/deploy-and-test.mdx +28 -13
  5. package/docs/articles/ci-cd-github/local-testing.mdx +36 -16
  6. package/docs/articles/ci-cd-gitlab/local-testing.mdx +22 -4
  7. package/docs/articles/github-deployment-testing.mdx +118 -31
  8. package/docs/articles/testing-getting-started.mdx +220 -0
  9. package/docs/articles/testing-preview-environments.mdx +148 -0
  10. package/docs/articles/testing-recipes.mdx +429 -0
  11. package/docs/articles/testing.mdx +138 -408
  12. package/docs/mcp-gateway/auth/configuring-auth0.mdx +6 -5
  13. package/docs/mcp-gateway/auth/configuring-clerk.mdx +4 -4
  14. package/docs/mcp-gateway/auth/configuring-cognito.mdx +5 -4
  15. package/docs/mcp-gateway/auth/configuring-entra.mdx +5 -4
  16. package/docs/mcp-gateway/auth/configuring-generic-oidc.mdx +8 -8
  17. package/docs/mcp-gateway/auth/configuring-google.mdx +4 -4
  18. package/docs/mcp-gateway/auth/configuring-keycloak.mdx +4 -3
  19. package/docs/mcp-gateway/auth/configuring-logto.mdx +4 -4
  20. package/docs/mcp-gateway/auth/configuring-okta.mdx +3 -3
  21. package/docs/mcp-gateway/auth/configuring-onelogin.mdx +3 -3
  22. package/docs/mcp-gateway/auth/configuring-ping.mdx +3 -3
  23. package/docs/mcp-gateway/auth/configuring-workos.mdx +5 -4
  24. package/docs/mcp-gateway/auth/manual-oauth-testing.mdx +8 -8
  25. package/docs/mcp-gateway/auth/overview.mdx +17 -17
  26. package/docs/mcp-gateway/auth/upstream-oauth.mdx +5 -5
  27. package/docs/mcp-gateway/code-config/local-development.mdx +14 -12
  28. package/docs/mcp-gateway/code-config/overview.mdx +9 -4
  29. package/docs/mcp-gateway/connect-clients/chatgpt.mdx +114 -56
  30. package/docs/mcp-gateway/how-it-works.mdx +11 -9
  31. package/docs/mcp-gateway/introduction.mdx +3 -1
  32. package/docs/mcp-gateway/quickstart-local.mdx +7 -7
  33. package/docs/mcp-gateway/reference.mdx +58 -25
  34. package/docs/mcp-gateway/server-registry.mdx +179 -0
  35. package/docs/mcp-gateway/test-clients.mdx +2 -2
  36. package/docs/mcp-server/custom-tools.mdx +32 -0
  37. package/docs/programmable-api/mcp-gateway-plugin.mdx +137 -0
  38. package/docs/programmable-api/mcp-sdk.mdx +240 -0
  39. package/docs/self-hosted/overview.md +2 -0
  40. package/package.json +5 -5
@@ -1,65 +1,97 @@
1
1
  ---
2
2
  title: Testing Your API
3
- sidebar_label: Testing
3
+ sidebar_label: Overview
4
+ description:
5
+ Learn how `zuplo test` runs the same TypeScript test files against your local
6
+ dev server, preview deployments, and production to catch configuration defects
7
+ that unit tests miss.
4
8
  ---
5
9
 
6
- Zuplo provides multiple ways to test your gateway at every stage of development.
7
- Whether you are iterating locally, reviewing a pull request in a preview
8
- environment, or gating production deployments in CI/CD, the
9
- [`zuplo test`](../cli/test.mdx) command and the `@zuplo/test` library give you a
10
- consistent testing experience.
10
+ Your gateway holds authentication, authorization, rate limits, routing, request
11
+ validation, and the shape of every error your consumers see. All of that is
12
+ configuration, and configuration is exactly what unit tests cannot reach. A unit
13
+ test can prove a JWT validation function rejects an expired token, but it cannot
14
+ prove the policy that calls that function is attached to the route your
15
+ customers hit.
11
16
 
12
- ## Testing strategies overview
17
+ `zuplo test` closes that gap. It runs plain TypeScript test files from your
18
+ repository against a live gateway, and it takes the target as a flag. The same
19
+ files run against your local dev server, against the preview deployment of your
20
+ branch, and against production.
13
21
 
14
- | Strategy | When to use | Endpoint target |
15
- | ---------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------ |
16
- | [Local testing](#local-testing) | Fast feedback while developing | `http://localhost:9000` |
17
- | [Preview environments](#preview-environments) | Validate changes on a real deployment before merging | `https://<branch>-<id>.zuplo.app` |
18
- | [CI/CD integration](#cicd-integration-testing) | Automated gate that blocks broken changes from reaching production | Deployment URL from your CI provider |
22
+ ```bash
23
+ npx zuplo test --endpoint http://localhost:9000
24
+ npx zuplo test --endpoint https://your-branch-abc123.zuplo.app
25
+ npx zuplo test --endpoint https://api.example.com --filter "smoke"
26
+ ```
19
27
 
20
- All three strategies use the same test files and the same `zuplo test` command.
21
- The only thing that changes is the `--endpoint` value.
28
+ New to `zuplo test`? Start with
29
+ [Get started with zuplo test](./testing-getting-started.mdx), which takes a
30
+ fresh project to a green run.
22
31
 
23
- ## Local testing
32
+ ## Same assertions, rising fidelity
24
33
 
25
- Running tests against a local development server gives the fastest feedback
26
- loop. Start the server with [`zuplo dev`](../cli/dev.mdx), then run your test
27
- suite against it.
34
+ Structure the suite as one set of assertions run at rising levels of fidelity.
35
+ Maintaining different tests per stage means the stage you trust least gets the
36
+ tests nobody maintains.
28
37
 
29
- ### Starting the local server
38
+ | Stage | When to use | Endpoint target |
39
+ | --------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------ |
40
+ | Local dev | Fast feedback while developing | `http://localhost:9000` |
41
+ | [Preview environment](./testing-preview-environments.mdx) | Validate a change on a real deployment before merging | `https://<branch>-<id>.zuplo.app` |
42
+ | [CI/CD gate](./github-deployment-testing.mdx) | Block a merge when gateway behavior regresses | Deployment URL from your CI provider |
43
+ | Production smoke checks | A small, read-only subset after deploy and on a schedule | Your production URL |
30
44
 
31
- ```bash
32
- npx zuplo dev
33
- ```
45
+ All of them use the same test files and the same command. The only thing that
46
+ changes is `--endpoint` — and, for production, a `--filter` that narrows the run
47
+ to the read-only subset.
34
48
 
35
- The gateway starts on `http://localhost:9000` by default. You can change the
36
- port with the `--port` flag. See the [`zuplo dev` reference](../cli/dev.mdx) for
37
- all available options.
49
+ :::tip
38
50
 
39
- ### Running tests locally
51
+ Nothing with side effects belongs in the production subset. Name those tests
52
+ consistently (a `smoke:` prefix works well) and select them by name.
40
53
 
41
- With the dev server running, open a second terminal and run:
54
+ :::
42
55
 
43
- ```bash
44
- npx zuplo test --endpoint http://localhost:9000
45
- ```
56
+ ## What to test
46
57
 
47
- The command discovers every `*.test.ts` file under the `tests/` folder and
48
- executes them against the provided endpoint.
58
+ The interesting assertions at a gateway are the rejections, because the happy
59
+ path hides the worst defects. A policy attached to the wrong route does not fail
60
+ — it succeeds for the wrong caller.
49
61
 
50
- :::tip
62
+ [Gateway test recipes](./testing-recipes.mdx) has a complete, copy-pasteable
63
+ file for each of these:
51
64
 
52
- You can filter which tests run with the `--filter` flag. For example,
53
- `npx zuplo test --endpoint http://localhost:9000 --filter 'auth'` runs only
54
- tests whose name contains "auth".
65
+ - **Auth rejections** missing token, expired token, wrong audience, and a
66
+ valid token from one tenant asking for another tenant's object
67
+ - **Rate limits, deterministically** — a fresh bucket per test, a sequential
68
+ loop, and an assertion on `429` plus `Retry-After`
69
+ - **OpenAPI conformance** — validate live responses against the
70
+ `routes.oas.json` that defines the routes
71
+ - **The error contract** — `application/problem+json` and the RFC 9457 members
72
+ - **Routing and CORS** — unknown paths, legacy aliases, preflight responses
73
+ - **Test data hygiene** — keep a parallel suite from corrupting itself
55
74
 
56
- :::
75
+ ## Local testing
76
+
77
+ Running against a local development server gives the fastest feedback loop.
78
+ Start the server with [`zuplo dev`](../cli/dev.mdx):
79
+
80
+ ```bash
81
+ npx zuplo dev
82
+ ```
57
83
 
58
- ### Testing with Zuplo services locally
84
+ The gateway starts on `http://localhost:9000` by default. In a second terminal:
85
+
86
+ ```bash
87
+ npx zuplo test --endpoint http://localhost:9000
88
+ ```
89
+
90
+ ### Test with Zuplo services locally
59
91
 
60
92
  Some features, such as API key authentication and rate limiting, require a
61
- connection to Zuplo cloud services. To use these features in local development,
62
- link your local project to an existing Zuplo project with
93
+ connection to Zuplo cloud services. To exercise those policies in local
94
+ development, link your local project to an existing Zuplo project with
63
95
  [`zuplo link`](../cli/link.mdx):
64
96
 
65
97
  ```bash
@@ -67,8 +99,8 @@ npx zuplo link
67
99
  ```
68
100
 
69
101
  Follow the prompts to select your account, project, and environment. This
70
- creates a `.env.zuplo` file that the dev server reads automatically. For local
71
- development, selecting the **development** environment is recommended.
102
+ creates a `.env.zuplo` file that the dev server reads automatically. We
103
+ recommend selecting the **development** environment for local development.
72
104
 
73
105
  :::warning
74
106
 
@@ -77,10 +109,10 @@ The `.env.zuplo` file can contain sensitive information. Add it to your
77
109
 
78
110
  :::
79
111
 
80
- Once linked, services like the API Key Authentication policy work locally using
81
- the same API key bucket as the linked environment. In the Zuplo Portal, open the
82
- [**Services**](https://portal.zuplo.com/+/account/project/services) tab in your
83
- project and select **API Key Service** to create API key consumers, then call
112
+ Once linked, the API Key Authentication policy works locally using the same API
113
+ key bucket as the linked environment. In the Zuplo Portal, open
114
+ [**Services**](https://portal.zuplo.com/+/account/project/services) in your
115
+ project and select **API Key Service** to create API key consumers. Then call
84
116
  your local gateway with the generated key:
85
117
 
86
118
  ```bash
@@ -88,393 +120,83 @@ curl http://localhost:9000/your-route \
88
120
  -H "Authorization: Bearer YOUR_API_KEY"
89
121
  ```
90
122
 
91
- For more details see
92
- [Connecting to Zuplo Services Locally](./local-development-services.mdx).
123
+ For more details, see
124
+ [connecting to Zuplo services locally](./local-development-services.mdx).
93
125
 
94
- ### Setting environment variables locally
126
+ ### Set environment variables locally
95
127
 
96
128
  Your local dev server does not have access to the environment variables
97
- configured in the Zuplo Portal. Instead, create a `.env` file in your project
98
- root:
129
+ configured in the Zuplo Portal. Create a `.env` file in your project root
130
+ instead:
99
131
 
100
132
  ```text title=".env"
101
133
  MY_BACKEND_URL=https://api.example.com
102
134
  MY_SECRET=supersecret
103
135
  ```
104
136
 
105
- The Zuplo CLI loads these variables automatically when you run `npx zuplo dev`.
106
- See
107
- [Configuring Environment Variables Locally](./local-development-env-variables.mdx)
108
- for more information.
137
+ The Zuplo CLI loads these variables automatically when you run `npx zuplo dev`,
138
+ and the same file supplies fixture values to `zuplo test`. See
139
+ [configuring environment variables locally](./local-development-env-variables.mdx).
109
140
 
110
- ## Preview environments
141
+ ## Test before merge
111
142
 
112
- Every branch pushed to your connected source control provider can create an
113
- isolated [preview environment](./environments.mdx). Preview environments are
114
- full Zuplo deployments that behave the same as production, making them ideal for
115
- testing pull requests before merging.
143
+ Every branch pushed to your connected source control provider deploys as a
144
+ preview environment: a full Zuplo deployment on the same edge network as
145
+ production, at its own URL. Running your suite there is the highest-fidelity
146
+ check available before a change merges, and it needs no shared staging
147
+ environment.
116
148
 
117
- ### Running tests against a preview environment
149
+ - [Testing preview environments](./testing-preview-environments.mdx) getting
150
+ the URL into your tests, per-environment services and secrets, and what to
151
+ scope where
152
+ - [Testing GitHub deployments](./github-deployment-testing.mdx) — trigger on the
153
+ `deployment_status` event and make the workflow a required status check
154
+ - [Custom CI/CD](./custom-ci-cd.mdx) — the same pattern for GitLab, Bitbucket,
155
+ Azure DevOps, and CircleCI
118
156
 
119
- Pass the preview environment URL as the endpoint:
157
+ :::caution{title="Poll for readiness, never sleep"}
120
158
 
121
- ```bash
122
- npx zuplo test --endpoint https://your-branch-abc123.zuplo.app
123
- ```
124
-
125
- Because preview environments run the full Zuplo runtime, including edge
126
- deployment, policies, and connected services, tests that pass here give high
127
- confidence that the changes work in production.
128
-
129
- ### Combining local and remote testing
130
-
131
- For maximum coverage, test locally first for fast iteration, then run the same
132
- test suite against the deployed preview environment:
133
-
134
- 1. Start local development and run tests against `http://localhost:9000`.
135
- 2. Push your branch. Zuplo deploys a preview environment automatically.
136
- 3. Run `npx zuplo test --endpoint <preview-url>` to verify behavior on the real
137
- edge deployment.
138
-
139
- ## CI/CD integration testing
140
-
141
- Automated tests in your CI/CD pipeline ensure that every deployment is validated
142
- before changes reach production. The `zuplo test` command works with any CI
143
- system.
144
-
145
- :::tip
146
-
147
- The examples below use the Zuplo GitHub integration. If you prefer setting up
148
- your own CI/CD for more fine-grained control, see
149
- [Custom CI/CD](./custom-ci-cd.mdx).
159
+ Most deployment APIs report success when a deployment is _accepted_, not when
160
+ the gateway is serving traffic. Tests fired into that gap fail, get retried, and
161
+ the pipeline race gets misfiled as flaky tests. Poll a health route with a
162
+ bounded deadline before running the suite — the CI guides above all show the
163
+ loop.
150
164
 
151
165
  :::
152
166
 
153
- ### Testing after deployment with GitHub Actions
154
-
155
- Using the Zuplo GitHub integration, tests can run after a deployment and block
156
- pull requests from being merged. The Zuplo Git Integration sets
157
- [Deployments](https://docs.github.com/en/rest/deployments/deployments) and
158
- [Deployment Statuses](https://docs.github.com/en/rest/deployments/statuses) for
159
- any push to a GitHub branch.
160
-
161
- Here is a simple GitHub Action that uses the Zuplo CLI to run the tests after
162
- the deployment is successful. Notice how the property
163
- `github.event.deployment_status.environment_url` is set to the `API_URL`
164
- environment variable. This is one way you can pass the URL where the preview
165
- environment is deployed into your tests.
166
-
167
- ```yaml title="/.github/workflows/main.yaml"
168
- name: Main
169
- on: [deployment_status]
170
-
171
- jobs:
172
- test:
173
- name: Test API Gateway
174
- runs-on: ubuntu-latest
175
-
176
- steps:
177
- - uses: actions/checkout@v4
178
- - name: Use Node.js
179
- uses: actions/setup-node@v4
180
- with:
181
- node-version-file: ".nvmrc"
182
-
183
- - name: Run Tests
184
- # Useful properties 'environment', 'state', and 'environment_url'
185
- run:
186
- API_URL=${{ toJson(github.event.deployment_status.environment_url) }}
187
- npx zuplo test --endpoint $API_URL
188
- ```
189
-
190
- ### Requiring status checks
191
-
192
- [GitHub Branch protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches)
193
- can be set in order to enforce policies on when a Pull Request can be merged.
194
- The example below sets the "Zuplo Deployment" and "Test API Gateway" as required
195
- status that must pass.
196
-
197
- ![Require status checks](../../public/media/testing/a1d7c322-125d-4d80-add0-fbfb65ccfea1.png)
198
-
199
- When a developer tries to merge their pull request, they will see that the tests
200
- haven't passed and the pull request can't be merged.
201
-
202
- ![Test failure](../../public/media/testing/3f3292a3-075c-4568-afb2-00c24e704f03.png)
203
-
204
- ### Local testing in CI
205
-
206
- You can also run tests against a local Zuplo server inside your CI pipeline
207
- before deploying anywhere. This catches issues earlier and avoids deploying
208
- broken changes.
209
-
210
- ```yaml title="/.github/workflows/local-test-then-deploy.yaml"
211
- name: Local Test Then Deploy
212
- on:
213
- push:
214
- branches:
215
- - main
216
- pull_request:
217
-
218
- jobs:
219
- local-test:
220
- runs-on: ubuntu-latest
221
- steps:
222
- - uses: actions/checkout@v4
223
- - uses: actions/setup-node@v4
224
- with:
225
- node-version: 24
226
- - name: Install dependencies
227
- run: npm install
228
- - name: Start local server and run tests
229
- run: |
230
- npx zuplo dev &
231
- DEV_PID=$!
232
- echo "Waiting for local server to start..."
233
- sleep 10
234
- npx zuplo test --endpoint http://localhost:9000
235
- kill $DEV_PID
236
-
237
- deploy:
238
- needs: local-test
239
- runs-on: ubuntu-latest
240
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
241
- env:
242
- ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}
243
- steps:
244
- - uses: actions/checkout@v4
245
- - uses: actions/setup-node@v4
246
- with:
247
- node-version: 24
248
- - name: Install dependencies
249
- run: npm install
250
- - name: Deploy to Zuplo
251
- run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
252
- ```
253
-
254
- For CI/CD examples with other providers, see
255
- [Custom GitHub Actions](./custom-ci-cd-github.mdx),
256
- [GitLab CI/CD](./custom-ci-cd-gitlab.mdx), and
257
- [CircleCI](./custom-ci-cd-circleci.mdx).
258
-
259
- ## Writing tests
260
-
261
- Using Node.js and the Zuplo CLI, it's very easy to write tests that make
262
- requests to your API using `fetch` and then validate expectations with `expect`
263
- from [chai](https://www.chaijs.com/api/bdd/).
167
+ ## Unit tests and mocking
264
168
 
265
- ```js title="/tests/my-test.test.ts"
266
- import { describe, it, TestHelper } from "@zuplo/test";
267
- import { expect } from "chai";
268
-
269
- describe("API", () => {
270
- it("should have a body", async () => {
271
- const response = await fetch(TestHelper.TEST_URL);
272
- const result = await response.text();
273
- expect(result).to.equal(JSON.stringify("What zup?"));
274
- });
275
- });
276
- ```
277
-
278
- Check out our
279
- [other sample tests](https://github.com/zuplo/zup-cli-example-project/tree/main/tests)
280
- to find one that matches your use-case.
281
-
282
- :::tip
283
-
284
- Your test files need to be under the `tests` folder and end with `.test.ts` to
285
- be picked up by the Zuplo CLI.
286
-
287
- :::
288
-
289
- ## Tips for writing tests
290
-
291
- This section highlights some of the features of the Zuplo CLI that can help you
292
- write and structure your tests. Check out our
293
- [other sample tests](https://github.com/zuplo/zup-cli-example-project/tree/main/tests)
294
- to find one that matches your use-case.
295
-
296
- ### Ignoring tests
297
-
298
- You can use `.ignore` and `.only` to ignore or run only specific test. The full
299
- example is at
300
- [ignore-only.test.ts](https://github.com/zuplo/zup-cli-example-project/blob/main/tests/ignore-only.test.ts)
301
-
302
- ```js title="/tests/ignore-only.test.ts"
303
- import { describe, it } from "@zuplo/test";
304
- import { expect } from "chai";
305
-
306
- /**
307
- * This example how to use ignore and only.
308
- */
309
- describe("Ignore and only test example", () => {
310
- it.ignore("This is a failing test but it's been ignored", () => {
311
- expect(1 + 4).to.equals(6);
312
- });
313
-
314
- // it.only("This is the only test that would run if it weren't commented out", () => {
315
- // expect(1 + 4).to.equals(5);
316
- // });
317
- });
318
- ```
319
-
320
- ### Filtering tests
321
-
322
- You can use the CLI to filter tests by name or regex. The full example is at
323
- [filter.test.ts](https://github.com/zuplo/zup-cli-example-project/blob/main/tests/filter.test.ts)
324
-
325
- ```js title="/tests/filter.test.ts"
326
- import { describe, it } from "@zuplo/test";
327
- import { expect } from "chai";
328
-
329
- /**
330
- * This example shows how to filter the test by the name in the describe() function.
331
- * You can run `zuplo test --filter '#labelA'`
332
- * If you want to use regex, you can do `zuplo test --filter '/#label[Aa]/'`
333
- */
334
- describe("[#labelA #labelB] Addition", () => {
335
- it("should add positive numbers", () => {
336
- expect(1 + 4).to.equals(5);
337
- });
338
- });
339
- ```
340
-
341
- ### Using environment variables in tests
342
-
343
- You can pass environment variables to your tests by setting them before the
344
- `zuplo test` command. Inside your test files, access them with `process.env`:
345
-
346
- ```bash
347
- MY_API_KEY=zpka_abc123 npx zuplo test --endpoint http://localhost:9000
348
- ```
349
-
350
- ```ts title="/tests/auth.test.ts"
351
- import { describe, it, TestHelper } from "@zuplo/test";
352
- import { expect } from "chai";
353
-
354
- describe("Authentication", () => {
355
- it("should reject requests without an API key", async () => {
356
- const response = await fetch(`${TestHelper.TEST_URL}/my-route`);
357
- expect(response.status).to.equal(401);
358
- });
359
-
360
- it("should accept requests with a valid API key", async () => {
361
- const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
362
- headers: {
363
- Authorization: `Bearer ${process.env.MY_API_KEY}`,
364
- },
365
- });
366
- expect(response.status).to.equal(200);
367
- });
368
- });
369
- ```
370
-
371
- ## Writing integration tests
372
-
373
- Integration tests verify that your gateway behaves correctly end-to-end,
374
- including routing, policies, and backend connectivity. Because `zuplo test` runs
375
- against a live endpoint (local or deployed), every test is inherently an
376
- integration test.
377
-
378
- ### Testing response status and headers
379
-
380
- ```ts title="/tests/headers.test.ts"
381
- import { describe, it, TestHelper } from "@zuplo/test";
382
- import { expect } from "chai";
383
-
384
- describe("Response headers", () => {
385
- it("should include CORS headers", async () => {
386
- const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
387
- method: "OPTIONS",
388
- headers: {
389
- Origin: "https://example.com",
390
- "Access-Control-Request-Method": "GET",
391
- },
392
- });
393
- expect(response.headers.get("access-control-allow-origin")).to.exist;
394
- });
395
-
396
- it("should return JSON content type", async () => {
397
- const response = await fetch(`${TestHelper.TEST_URL}/my-route`);
398
- expect(response.headers.get("content-type")).to.include("application/json");
399
- });
400
- });
401
- ```
402
-
403
- ### Testing rate limiting
404
-
405
- ```ts title="/tests/rate-limit.test.ts"
406
- import { describe, it, TestHelper } from "@zuplo/test";
407
- import { expect } from "chai";
408
-
409
- describe("Rate limiting", () => {
410
- it("should include rate limit headers", async () => {
411
- const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
412
- headers: {
413
- Authorization: `Bearer ${process.env.MY_API_KEY}`,
414
- },
415
- });
416
- expect(response.headers.get("ratelimit-limit")).to.exist;
417
- expect(response.headers.get("ratelimit-remaining")).to.exist;
418
- });
419
- });
420
- ```
421
-
422
- ### Testing request validation
423
-
424
- ```ts title="/tests/validation.test.ts"
425
- import { describe, it, TestHelper } from "@zuplo/test";
426
- import { expect } from "chai";
427
-
428
- describe("Request validation", () => {
429
- it("should reject invalid request bodies", async () => {
430
- const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
431
- method: "POST",
432
- headers: { "Content-Type": "application/json" },
433
- body: JSON.stringify({ invalid: "data" }),
434
- });
435
- expect(response.status).to.equal(400);
436
- });
437
-
438
- it("should accept valid request bodies", async () => {
439
- const response = await fetch(`${TestHelper.TEST_URL}/my-route`, {
440
- method: "POST",
441
- headers: { "Content-Type": "application/json" },
442
- body: JSON.stringify({ name: "Test", email: "test@example.com" }),
443
- });
444
- expect(response.status).to.equal(200);
445
- });
446
- });
447
- ```
448
-
449
- ## Unit Tests & Mocking
169
+ `zuplo test` suites and the gateway are separate processes that communicate over
170
+ HTTP. A test can mock anything in its own process — a helper that wraps `fetch`,
171
+ a clock, a fixture module — but nothing inside the gateway. To control what the
172
+ gateway sees, change the gateway instead: return canned responses with the
173
+ [Mock API policy](../policies/mock-api-inbound.mdx), disable a policy for test
174
+ traffic with the [Testing bypass policy](./bypass-policy-for-testing.mdx), or
175
+ point the route at your own stub server.
450
176
 
451
177
  :::caution{title="Advanced"}
452
178
 
453
- Custom testing can be complicated and is best used only to test your own logic
454
- rather than trying to mock large portions of your gateway.
179
+ Custom testing can be complicated. Use it to test your own logic rather than
180
+ trying to mock large portions of your gateway.
455
181
 
456
182
  :::
457
183
 
458
- It's usually possible to use test frameworks like
184
+ You can use test frameworks like
459
185
  [Mocha](https://github.com/zuplo/zuplo/tree/main/examples/test-mocks) and
460
- mocking tools like [Sinon](https://sinonjs.org/) to unit tests handlers,
461
- policies, or other modules. To see an example of how that works see this sample
462
- on GitHub: https://github.com/zuplo/zuplo/tree/main/examples/test-mocks
186
+ mocking tools like [Sinon](https://sinonjs.org/) to unit test handlers,
187
+ policies, or other modules. For an example, see this
188
+ [sample on GitHub](https://github.com/zuplo/zuplo/tree/main/examples/test-mocks).
463
189
 
464
- Do note though that not everything in the Zuplo runtime can be mocked.
465
- Additionally, internal implementation changes might cause mocking behavior to
466
- change or break without notice. Unlike our public API we don't guarantee that
467
- mocking will remain stable between versions.
190
+ Not everything in the Zuplo runtime can be mocked, and internal implementation
191
+ changes might cause mocking behavior to change or break without notice. Unlike
192
+ the public API, mocking is not guaranteed to remain stable between versions.
468
193
 
469
- Generally speaking, if you must write unit tests, it's best to test your logic
470
- separately from the Zuplo runtime. For example, write modules and functions that
471
- take all the arguments as input and return a result, but don't depend on any
472
- Zuplo runtime code.
194
+ If you must write unit tests, test your logic separately from the Zuplo runtime.
195
+ Write modules and functions that take all their arguments as input and return a
196
+ result, without depending on Zuplo runtime code.
473
197
 
474
198
  For example, if you have a function that uses an environment variable and want
475
- to unit test it.
476
-
477
- Don't do this:
199
+ to unit test it, don't do this:
478
200
 
479
201
  ```ts
480
202
  import { environment } from "@zuplo/runtime";
@@ -485,7 +207,7 @@ export function myFunction() {
485
207
  }
486
208
  ```
487
209
 
488
- Instead do this:
210
+ Do this instead:
489
211
 
490
212
  ```ts
491
213
  export function myFunction(myVar: string) {
@@ -493,13 +215,13 @@ export function myFunction(myVar: string) {
493
215
  }
494
216
  ```
495
217
 
496
- Then write your test like this:
218
+ Then write your test:
497
219
 
498
220
  ```ts
499
221
  import { myFunction } from "./myFunction";
500
222
 
501
223
  describe("myFunction", () => {
502
- it("should return Hello World", () => {
224
+ it("returns Hello World", () => {
503
225
  expect(myFunction("World")).to.equal("Hello World");
504
226
  });
505
227
  });
@@ -509,7 +231,7 @@ describe("myFunction", () => {
509
231
 
510
232
  If you are running unit tests in a Node.js environment, you may need to polyfill
511
233
  some globals. Zuplo itself doesn't run on Node.js, but because Zuplo is built on
512
- standard API, testing in Node.js is possible.
234
+ standard APIs, testing in Node.js is possible.
513
235
 
514
236
  Node.js 24, the minimum version the Zuplo CLI supports, includes the `webcrypto`
515
237
  module, which polyfills the `crypto` global. You must register this polyfill
@@ -521,3 +243,11 @@ if (typeof crypto === "undefined") {
521
243
  globalThis.crypto = webcrypto;
522
244
  }
523
245
  ```
246
+
247
+ ## Related
248
+
249
+ - [Get started with zuplo test](./testing-getting-started.mdx)
250
+ - [Gateway test recipes](./testing-recipes.mdx)
251
+ - [Testing preview environments](./testing-preview-environments.mdx)
252
+ - [Testing GitHub deployments](./github-deployment-testing.mdx)
253
+ - [`zuplo test` CLI reference](../cli/test.mdx)