Skip to content
Novus Examples

Schema and API Fixture Testing With Valid and Invalid Twins

Exercise OpenAPI, JSON Schema, pagination, and webhook parsers against deterministic valid twins and intentionally invalid SAMPLE payloads.


API and schema tooling needs two outcomes: accept the good payload and reject the bad one loudly. Hand-written one-off JSON in unit tests drifts; documented fixtures with permanent paths do not.

Valid ↔ invalid as a unit

Browse purposes schema-testing and json-parsing. For each pair:

  1. Load the valid twin — assert $ref resolution, required fields, and example request/response shapes.
  2. Load the intentionally invalid twin — assert the error path (path pointer, code, message) matches your contract.
  3. Never “fix up” invalid fixtures in production code paths; they exist to prove failure.

Titles and descriptions call out invalid samples so humans and scrapers do not treat them as golden masters.

Assert the error, not just the failure

The most common weak test in this area is expect(validate(payload)).toBe(false). That passes whether your validator rejected the payload for the documented reason or choked on something unrelated — and it will keep passing after you break the error path entirely. For every invalid twin, assert three things:

  1. Which field failed. A JSON Pointer (/order/items/0/price) or equivalent path. If your validator only reports "invalid", you cannot build a useful error message on top of it, and no test will tell you.
  2. Why it failed. The specific keyword — required, type, enum, format, minimum. Swapping a type error for a required error is a real regression that a boolean assertion cannot see.
  3. How many failures came back. Validators differ on fail-fast versus collect-all, and the difference shows up in your UI as "one error at a time" versus a full form of red. Pin the count so a library upgrade that silently changes this mode is caught here rather than in a bug report.

Pagination, GraphQL, webhooks

API fixtures cover page cursors, GraphQL-shaped responses, and webhook envelopes. Pin /files/data/... paths in CI. When you add a new field to your client, add an assertion against the documented specs table rather than inventing another ad-hoc blob.

Two edges here reward a dedicated fixture. The empty page — a valid response with zero items and a null cursor — is the shape that breaks naive while (cursor) loops and "take the last item's id" pagination. And the last page, where the cursor is absent rather than null, separates clients that check for undefined from those that only check for null. Both are legal responses; both routinely crash consumers that were only ever tested against a full page.

For webhooks, remember the envelope is a contract in its own right. Assert that unknown top-level fields are tolerated (a provider adding a field must not break you) while unknown fields inside a strictly-typed payload are rejected. Getting that asymmetry backwards is how a client either becomes impossible to evolve or silently accepts garbage.

Time series edges

Timeseries-testing fixtures include irregular timestamps, DST gaps, and duplicate keys. Assert your normalizer’s policy (drop, keep-last, error) explicitly—silent coercion is how production incidents start.

Keep the fixture and the schema in step

The failure mode that outlives every other one on this list: a fixture that no longer matches the schema it was written for. It happens quietly — someone adds a required field, the valid twin is never updated, and the test that was supposed to prove acceptance now proves nothing because it was already asserting a stale shape.

Two habits prevent it:

  • Validate the valid twins in CI, every run, as their own test — not only as inputs to a client test. If a fixture stops validating against the current schema, that is the alarm, and it should fire before anything downstream does.
  • Assert the invalid twins fail for exactly one documented reason. An invalid fixture that accumulates a second, accidental error still fails the test while no longer testing what it claims to. Pinning the error path and count (above) is what catches this.

SAMPLE only

Config and “secrets-shaped” files use fictional values only. There are no real credentials in the library—treat anything that looks like a key as a shape test, not a secret to protect.