JSON Schema Validation for Test Data Generation
Most teams reach for JSON Schema after something has already gone wrong — a malformed payload slipped past a client, and now every request gets validated against a schema before it's accepted. That's a useful safety net, but it undersells what JSON Schema is good for. The same constraints that reject bad data at the door can also drive the generation of good data in the first place, and running them again after generation catches problems that generation alone won't.
Constraints as a generation spec, not just a filter
A JSON Schema document already describes almost everything you need to
generate a plausible fixture: type tells you the shape, format
tells you whether a string should look like an email, a date-time, or a
UUID, enum gives you the exact closed set of valid values, pattern
gives you a regex a string must satisfy, and required tells you which
fields can't be left out. Treat that as a generation spec instead of a
rejection filter and you get data that's valid by construction — no
separate step where you guess at constraints the schema already states
explicitly.
This matters because hand-written or naive random fixtures routinely
drift from what a schema actually allows. A status field typed as a
free string in a mock generator will happily produce "Shipped" when
the real enum is ["pending", "shipped", "delivered", "cancelled"]
in lowercase. A format: "email" field will get "user123" instead
of something that passes an email format check. Generating directly
from the schema's constraints avoids the gap entirely, because the
constraints define the space of valid values you're sampling from,
not a suggestion you might drift away from.
Realistic data still needs schema-aware generation
Schema constraints and realism aren't in tension — they're
complementary. format: "date" tells a generator to produce a valid
date, but a domain-aware generator also knows a shipped_at date
should come after created_at. pattern can enforce that a SKU looks
like SKU-\d{6}, but it can't tell you which prefixes actually exist
in your catalog. The schema defines the boundary of validity; realistic
generation fills that boundary with values that also make business
sense. Schema-first generation gets you the boundary for free, which
means less code has to be written to reconstruct constraints that were
already declared once, correctly, in the schema.
Validating fixtures catches drift before your tests do
The other half of the story is validation after generation, not just
before. Schemas evolve — a field goes from optional to required, an
enum gains a new value, a string field is tightened with a new
pattern. If your test fixtures are static files checked into a repo,
or generated by code that was written against an older version of the
schema, they can quietly stop matching reality. Nothing breaks
immediately, because the fixtures are just data sitting in a file. The
failure shows up later, and confusingly, when a test that consumes
that fixture fails for reasons that have nothing to do with the code
under test.
Re-validating every generated fixture against the current schema as a
build or CI step turns that into an immediate, specific failure right
where the drift happened: "fixture order-42.json is missing required
field discount_code," not "checkout test failed, good luck." That's
a cheap check — schema validation is fast — and it converts a class of
flaky, hard-to-diagnose test failures into a clear, actionable error at
the moment the mismatch is introduced.
Why this belongs in the generation pipeline, not bolted on after
Bolting schema validation onto an existing ad-hoc data generator is better than nothing, but it means maintaining two independent descriptions of what "valid" means: the schema, and whatever logic the generator happens to encode. Every time the schema changes, someone has to remember to update the generator to match, and validation is what catches it when they forget — after the fact, as a symptom rather than a cause.
Driving generation directly from the schema collapses that to one source of truth. Update the schema, and both the shape of newly generated data and the check that validates it move together, automatically. That's the difference between JSON Schema as a rejection filter bolted onto the end of a pipeline, and JSON Schema as the spec the pipeline is built from.