Test Data Generation Guide: Build Fixtures That Check Real Rules

Build test data from schema constraints, parent-child relationships and expected outcomes. Includes a worked order fixture and field-specific examples.

By Vincent RuanPublished June 25, 2026Last updated September 6, 20265 min read

Illustration for this article: Build test data from schema constraints, parent-child relationships and expected outcomes. Includes a worked order fixture and field-specific examples.

A test data generation guide should leave you with inputs and expected outcomes. Start by naming the behavior: a checkout accepts an existing customer, an import preserves a leading-zero postcode, or an API rejects an impossible date. Generate data to exercise that rule, then verify the result.

This workflow covers application fixtures and database seeding. For ready-made person rows, the bulk exporter provides CSV, JSON and SQL. Custom entities, foreign keys and business constraints still belong in your fixture builder.

1. Write a small data contract

FieldSuccess-path requirementIntentional failure case
customer_idMatches an existing customer keyUnknown customer c999
amount_centsInteger greater than zeroZero or negative amount
ordered_atISO timestamp in UTCUnparseable timestamp
shipped_atNull or at/after ordered_atShipment before order
emailA fictional example-domain valueMalformed input in the validation suite
postal_codeText, preserving leading zerosNumeric import removes a leading zero
Example contract for an order-import test. These are explicit application choices, not universal field rules.

A positive amount is the rule for this import example; a refund system could need negatives. Make that distinction explicit before generating. Otherwise a generic validator can reject legitimate domain data while the test suite appears correct.

2. Generate parents before children

Create customers c001, c002 and c003 first. Then pick order.customer_id values from those three keys. Do not generate independent random customer IDs for the orders and hope they join. The deliberately invalid order below belongs in a separate rejection test.

Ordercustomer_idamount_centsExpected result
o001c0011299Accept
o002c001500Accept; one customer may have multiple orders
o003c0032500Accept
o004c9991000Reject unknown customer
o005c002-100Reject amount under this contract
Worked fixture: parent key set {c001, c002, c003}; amounts are integer cents.

This five-row example contains three accepted rows and two rejected rows by construction. Assert each result and its error reason. Import only the first three when setting up a successful order-list test; use the other two to test the importer’s rejection behavior. The counts are fixture arithmetic, not a production error-rate measurement.

3. Choose a generation method by the constraint

MethodUse whenWork you still own
Hand-written fixturesA small set of named boundary or regression cases mattersMaintain expected outcomes when the contract changes
Faker or a similar libraryYou need many ordinary rows in an automated suiteDomain relationships, unique keys and explicit edge cases
A browser exporterSomeone needs a file without writing generation codeMap its fixed fields into the target schema
A controlled source-derived datasetA test needs measured distributions or a specific production defectAccess, provenance, disclosure review and refresh policy
Selection guide based on what you need to control; no invented 1–5 quality scores.

Do not assume that a masked production copy is anonymous, or that every synthetic generator is independent of source records. NIST SP 800-188 describes de-identification approaches and the governance needed around them [nist]. If your test needs no production records, a source-independent fixture avoids copying those records in the first place.

4. Apply the right rule to each field

FieldUseful fixture choiceLimit
Emailreader@example.com for a display or syntax case [iana-domains]Use a controlled mail sink to test actual messages
Card paymentA documented processor test scenario [stripe]A Luhn pass is not authorization; use test credentials
SSN-shaped inputAn excluded area such as 000 in a rejection test [ssa]Nine-digit shape does not make an issued or valid SSN
Postal addressA verified city/region/postcode tuple plus a fictional streetA matching tuple does not verify delivery to that street
BirthdayA fixed reference date and named leap-day policyA random date need not cover age boundaries
Format, semantics and integration outcomes are different checks.

Use the US address format guide for field layout and the Stripe test-card guide for processor scenarios. A country code chooses conventions; it does not independently validate geography or a delivery point.

5. Add explicit boundaries beside ordinary rows

Do not wait for random generation to produce the case you need. The four validation articles provide fixed downloadable inputs with stated expectations: names, emails, phone numbers and birthdays.

  • For a text field with a declared maximum, test one below, exactly at and one above it; state whether the limit counts bytes, code points or another unit.
  • For an optional field, test missing, null and empty string separately when your schema distinguishes them.
  • For a timestamp pair, test equal times, correct order and reversed order.
  • For a relationship, test no children, one child, several children and an orphan key.

6. Record the recipe and verify the import

Faker documents why a seed is insufficient when dependencies change or a method uses a relative date [faker]. Record the exact version, locale, seed, call order and reference date. The deterministic seeding guide includes a runnable example with measured output.

  1. Validate the success-path file against the real schema before importing.
  2. Run rejection fixtures separately and assert the named errors.
  3. Check row counts, key uniqueness, joins and date ordering after import.
  4. Import postal codes and other identifiers as text; confirm that leading zeros and Unicode survive.
  5. Reset or namespace generated records so rerunning a test does not introduce duplicates.
  6. Save the exact failing row with its configuration when debugging a regression.

A generated dataset is useful when you can say what it tests and show the expected result. Large row counts help load tests; a five-row fixture may be enough to prove a relationship rule. Choose the smallest dataset that answers the test’s question, then add volume only when volume is part of that question.

References & sources

  1. SP 800-188: De-identification techniques and governance — NIST
  2. Example Domains — IANA
  3. Testing payments with test cards and credentials — Stripe
  4. Social Security Number Randomization: excluded numbers — Social Security Administration
  5. Faker usage: reproducible results — Faker

Frequently asked questions

How do I generate useful test data?

Define the test behavior and field constraints, create parent records, generate children from existing keys, add deliberate boundary cases, and check expected outcomes before importing. Record the generation configuration and reset the environment between runs.

Should all generated rows pass validation?

Only the success-path dataset should. Rejection tests need deliberately invalid rows with named expected errors. Keep them separate so an intentional error cannot accidentally break setup for an unrelated test.

Does a country locale make an address valid?

No. A locale chooses language and formatting conventions. City, region and postcode need a verified relationship, and a full street address needs separate delivery-point validation when the test requires actual delivery.

Is a random Luhn-valid number a payment test card?

No. The checksum only detects certain input errors. Use your processor’s documented test card or payment method with test credentials, and choose the documented scenario you want to exercise.

Does a seed reproduce an entire dataset?

Only with the same version, source data, locale, generation order and clock inputs. Record those settings and preserve important failing rows as fixed fixtures.

More on test data practice

Put this to work