Skip to main content

Testing workflow

Reviewed

Build deterministic test fixtures you can replay

A practical workflow for repeatable API fixtures: pin the seed, every shape parameter and any date window or as-of anchor, then save the resolved contract.

The task
Create a fixture that stays useful across local runs and CI without freezing accidental defaults into the test.
Review state

Direct answer

Direct answer

For a replayable fixture, keep the seed and every parameter that changes record shape, and make time explicit. A seed controls the pseudo-random choices; it does not turn an omitted clock-derived default into a permanent date. Prefer an endpoint with an explicit asOf, from/to window or datasetKey, then assert business invariants rather than an entire incidental payload.

Decision support

Choose the route by the job

When you need Choose Why
Several related database tables /relational-seed It returns one internally consistent bundle and can pin the causal clock with asOf.
Random instants in a known test window /dates Explicit from/to plus seed makes the sampled instants replayable; weekdaysOnly is an exact UTC weekday constraint.
Payment-list states and currency edge cases /transactions Type, status, amount, currency and date filters constrain every returned record when the range is explicit.
Timed and all-day scheduling UI fixtures /calendar-events It separates absolute timed instants from exclusive-end all-day date shapes and accepts fixed bounds.
Paging, infinite-scroll and empty-boundary tests /pagination datasetKey, ordering and total define a clock-free fixture; cursors bind those exact settings.
01

What does a seed actually control?

A seed controls the generator's repeatable choices for the same resolved parameters. Parameters remain part of the fixture identity, including defaults that the server resolves from the current date.

Treat the fixture key as a tuple: endpoint, route, seed and resolved parameters. If count, locale, status, currency, schema or a range changes, a different response is expected. This is useful: tests can vary one dimension deliberately without pretending that seed alone owns the whole contract.

For generated list routes, randomapi.dev derives each record from its own position. That gives prefix stability: increasing count keeps the earlier prefix stable for routes covered by the generated-list contract. Relational Seed is stricter about one growth axis: raising customers keeps the existing parent bundles stable, while ordersPerCustomer or itemsPerOrder reshapes child tables and is therefore intentionally not append-only.

02

How do you remove clock drift from a fixture?

Pass the date boundary yourself. Use asOf where it exists, or supply both from and to; then keep those values with the seed in the test case.

An omitted time window can still be deterministic for the resolved day while moving tomorrow. Dates defaults to the 365 days ending at the current UTC midnight. Transactions defaults to a recent window. Calendar Events derives a UTC-day-snapped future window when neither bound is supplied. Those defaults are convenient for demos, but they are the wrong foundation for a long-lived golden fixture.

The safe pattern is explicit input, not a copied output with no explanation. Pin asOf for a relational bundle, pin from/to for dates, transactions or events, and keep the response's meta.params near the test failure. That makes a changed answer diagnosable: either the inputs changed, the contract changed, or a regression occurred.

  1. 01

    Choose the smallest domain-specific endpoint

    A payment test should ask Transactions for failed payments; a database-relation test should use Relational Seed rather than joining unrelated flat generators.

  2. 02

    Pin every shape and time input

    Record seed, count, filters, locale and explicit asOf or from/to values. Avoid relying on a current-date default in a snapshot test.

  3. 03

    Assert invariants before snapshots

    Check foreign keys, bounds, status/date masks and exact totals. Snapshot only the small fields whose literal stability is the behavior under test.

  4. 04

    Keep a replay URL in the failure output

    A complete request is a compact reproduction case that can be opened without rebuilding the original UI or test harness.

03

What should the test assert?

Assert the promise the endpoint makes: bounds, relationships, ordering and masks. Whole-response snapshots are strongest only when every byte is intentionally contractual.

For Relational Seed, verify that each non-null foreign key points to a parent in the same response and that money totals reconcile in integer minor units. For Dates, verify that every instant sits inside the explicit range and that weekday-only records are Monday through Friday in UTC. For Transactions, check the currency, type, status and amount/date bounds you requested. For Pagination, walk pages and ensure there are no gaps or duplicates.

The response envelope helps with failure messages. meta.params is the server's statement of the values it actually honored, including resolved defaults. Save or print that object when a test fails; it is usually more useful than a large undifferentiated JSON diff.

  • The request contains no omitted clock-derived input that the test assumes is fixed.
  • The seed and every shape parameter are visible in the fixture setup.
  • Assertions cover domain invariants, not only record count or JSON shape.
  • The test can print one complete replay URL and meta.params on failure.
  • The fixture contains only synthetic data and reserved/example destinations where applicable.

Working requests

Run the concrete examples

A replayable relational SaaS bundle

Pins the generator, schema size and causal time anchor. Every non-null foreign key resolves inside this response, and no generated timestamp is later than asOf.

curl request
curl -s 'https://randomapi.dev/api/relational-seed?schema=saas&customers=6&ordersPerCustomer=3&itemsPerOrder=2&seed=42&asOf=2026-01-15T09%3A00%3A00Z'

Inspect the response

  • meta.params records seed 42 and the resolved 2026-01-15T09:00:00.000Z anchor.
  • tableOrder is plans → accounts → users → invoices → invoice_items.
  • All five declared foreign keys reference a table earlier in tableOrder.
  • Every actual non-null foreign-key value resolves to a parent row returned in the same bundle.
  • Every non-null timestamp field in the returned tables is at or before the 2026-01-15T09:00:00Z asOf anchor.

Stable pagination boundary data

Creates a deterministic final partial page without a random seed or hidden state. datasetKey names the stable fixture namespace.

curl request
curl -s 'https://randomapi.dev/api/pagination/offset?total=53&page=6&pageSize=10&sort=rank&order=desc&datasetKey=leaderboard'

Inspect the response

  • The last page contains exactly three items.
  • pageInfo is page 6 of 6, covering items 51–53 with no next page.
  • meta.params pins total 53, rank/descending order, datasetKey leaderboard, page 6 and pageSize 10.

Material limits

Caveats that change the decision

  • Seeded does not mean cryptographically random. Reproducible UUID-shaped or token-shaped fixture values must not be used as secrets or production identifiers.
  • Date-only bounds mean UTC midnight on the date. If a whole final day must be eligible, supply its end-of-day timestamp or use a date-shaped endpoint whose end boundary is explicitly exclusive.
  • A fixed seed cannot rescue a moving default. Pin asOf or both range boundaries for a durable fixture.
  • Relational Seed is internally consistent but intentionally does not match /api/users, /api/products or /api/orders for the same seed.

Endpoint reference

APIs used in this guide

Visible provenance

Sources used by this guide