How Do You Generate Realistic Test Data for APIs and Databases?
Realistic test data is the stuff that makes your API tests, seed scripts, and database checks behave like the real system without copying live customer records into a sandbox. You want valid formats, believable values, and the right relationships, not a pile of random strings that only survive the first validation rule. If you need a quick way to spin up sample records, use this random data generator tool to get a clean starting set.
Start With the Schema, Not the Randomness
The mistake most teams make is generating fake names and emails first, then trying to cram them into tables that expect foreign keys, enums, and unique constraints. Start by listing the entities your app actually uses: users, orders, invoices, devices, sessions, tickets, or whatever sits in your JSON payloads and tables. Then define which fields are required, which must be unique, and which values are controlled by business logic.
For each model, answer three questions before generating anything: what must exist, what must be consistent, and what can safely vary. That keeps your seed data aligned with the real app instead of some imaginary happy path. A useful rule: if the database or API would reject it in production, your test data should reject it too, unless you are specifically testing validation failures.
Pay special attention to identity fields, timestamps, and relationships. Primary keys, UUIDs, slugs, created-at values, parent IDs, and status fields usually carry more meaning than the visible text around them. If those are wrong, your tests may pass for the wrong reason or fail for reasons unrelated to the feature under test.
Make Relationships Look Real
Good test data does not just look valid in isolation. It also needs believable linkages between records so joins, pagination, permission checks, and rollups behave the way they do in production. A user should have orders, an order should have line items, and a subscription should point to a customer that actually exists.
If you are testing an API that returns nested objects, build the parent objects first and derive children from them. If you are testing a relational database, insert rows in dependency order: reference tables, then parent rows, then child rows. This avoids orphaned rows and gives you predictable fixtures when a test needs to fetch a record by ID.
For example, if an order contains 3 line items, do not generate 17 line items because that was the easiest random number. Use distributions that mirror reality. Most orders are small, some are medium, and a few are noisy edge cases.
When you need repeatable structure, pair random values with fixed templates. A user might always have an email, timezone, and role, while the actual name and signup date vary. That gives you test data that feels organic without turning each run into a debugging séance.
Keep Validation, Enums, and Edge Cases Intact
Realistic data is not all “nice” data. It includes the annoying stuff production systems always collect: missing optional fields, unusually long names, old timestamps, cancelled subscriptions, partial refunds, inactive accounts, and records with zero child rows. Those cases are where bugs hide, especially in UI lists, exports, and reporting queries.
Build a small checklist for each table or payload:
- Required fields are always present.
- Enum values come from the real allowed set.
- Unique fields never collide.
- Dates make sense relative to one another.
- Nulls and empty strings only appear where they are valid.
For APIs, this matters even more because request and response shape often drift over time. A field that is optional in the schema may still be assumed by frontend code. Seed one or two records that omit that field so you can catch brittle consumers early.
If you are building test fixtures for validation tests, keep those separate from your “normal” realistic set. One dataset should represent expected production-like behavior. Another should be deliberately malformed. Mixing them together makes it harder to know whether a failure is a bug or just bad input doing its job.
Use Generators for Volume, Then Tweak the Details
Hand-writing 200 users or 5,000 event rows is a waste of time and a good way to introduce inconsistent data. Use a generator for scale, then edit the pieces that need domain-specific meaning. That gives you speed without turning your fixtures into nonsense.
The best workflow is usually:
- Generate a batch of base records.
- Normalize the formats your app expects.
- Patch in dependencies and reference IDs.
- Add targeted edge cases by hand.
- Run the dataset through your validators or import scripts.
This is also where a dedicated tool earns its keep. If your app needs fake names, emails, phone numbers, IDs, or dates in a hurry, generate them in bulk and then shape them to fit your schema. For dates in particular, pair the dataset with our guide on generating random dates for testing date-range bugs when you need realistic ranges instead of one giant block of timestamps from 2025.
Volume is useful for performance testing too. A query that works fine with 12 rows may become a very different animal with 120,000. You do not need production-scale data to find bottlenecks, but you do need enough rows to make indexes, joins, and sort operations sweat a little.
Sanitize Real Data Before You Copy It
Sometimes the most realistic test data starts as production data. That is fine, but only after you scrub it properly. Remove names, emails, phone numbers, addresses, payment details, and anything that could identify a real person or account. Then replace those fields with synthetic values that preserve the shape and constraints of the original records.
Scrubbing is not just a privacy step. It is also a schema step. If you preserve the format of a postal code, phone number, or account ID, your downstream validation still behaves like the live system. If you flatten everything into generic placeholders, you lose the very bugs you were trying to catch.
One practical trick is to anonymize fields while keeping references stable. For example, keep the same user ID across related tables, but change the name and email attached to that ID. That preserves joins and ownership logic while removing personal data.
Production-shaped data without production secrets is the goal. Keep the structure, constraints, and relationships. Lose the identities.
If your dataset includes logs or event streams, keep the sequencing believable too. A password reset should not happen before account creation. An order cannot be refunded before it exists. These sound obvious until you see a fixture that was assembled by five different scripts and one caffeinated human.
Choose the Right Shape for APIs and Databases
API payloads and database rows overlap, but they are not the same job. APIs care about request shape, response shape, and pagination behavior. Databases care about constraints, normalization, indexes, and referential integrity. Good realistic test data respects both without trying to make them identical.
For API testing, include records that exercise common client assumptions. Give one user a long display name, another a missing avatar, and another a stale token. For database testing, include foreign keys, duplicate candidate values, indexed columns with varied cardinality, and enough skew to show whether your query plans are actually useful.
If you are testing a REST endpoint, this kind of payload is more useful than a totally random blob:
{
"id": "7f2d8c5a-6c0b-4ad2-9a4b-2f3bc8a6d911",
"email": "maria.chen@example.test",
"role": "admin",
"status": "active",
"created_at": "2025-01-14T09:22:31Z",
"last_login_at": "2025-07-03T16:05:10Z"
}That record tells a story the app can work with. It has a valid UUID, a plausible email, a controlled role, and timestamps that make sense. The same logic applies to SQL rows, CSV fixtures, and JSON documents imported into a warehouse.
A Worked Example
Suppose you need sample data for a billing API with users, subscriptions, and invoices. A bad fixture might look random but still fail in real usage because the invoice references a missing user, or the subscription end date comes before the start date. A better fixture keeps the relationships tight and the dates believable.
Before:
users:
- id: 1
email: "abc@x.com"
subscriptions:
- id: 9
user_id: 42
plan: "platinum"
start_at: "2025-10-01"
end_at: "2025-09-01"
invoices:
- id: 1001
subscription_id: 8
amount_cents: -500After:
users:
- id: 1
email: "jordan.lee@example.test"
full_name: "Jordan Lee"
status: "active"
subscriptions:
- id: 9
user_id: 1
plan: "pro"
start_at: "2025-01-01"
end_at: "2025-12-31"
auto_renew: true
invoices:
- id: 1001
subscription_id: 9
amount_cents: 4900
currency: "USD"
issued_at: "2025-02-01T00:00:00Z"
paid_at: "2025-02-01T00:03:12Z"The second version is not just prettier. It lets you test account ownership, subscription status logic, invoice rendering, and time-based filtering without tripping over fake brokenness. That is what realistic test data should do: support the test, not become the test.
Frequently Asked Questions
How do you generate realistic test data for an API?
Start with the request and response schemas, then generate records that satisfy every required field, enum, and relationship. Use believable values for names, dates, IDs, and status fields, and make sure the data matches the way the API is actually consumed. If the API supports pagination, sorting, or filtering, include records that exercise those code paths too.
What is the best way to create fake database records?
Build parent records first, then child records that reference them through real foreign keys. Use a generator for bulk values, then patch in domain-specific constraints like unique emails, valid timestamps, and allowed status values. A mix of repeatable templates and random data usually works better than fully random rows.
Should test data look exactly like production data?
It should look like production data in shape and behavior, but it should not contain real personal or financial information. Keep formats, relationships, and edge cases intact, then replace sensitive values with synthetic ones. That gives you realistic behavior without privacy risk.
How do I avoid duplicate values in test data?
Track unique fields separately and generate them from a constrained namespace, such as incrementing IDs, UUIDs, or deterministic email patterns. If collisions matter, validate each batch before import and regenerate any conflicting values. This is especially important for usernames, order numbers, slugs, and external IDs.
Wrapping Up
Realistic test data is not about randomness for its own sake. It is about generating records that obey the same rules your app has to survive in production: schema, relationships, timing, uniqueness, and edge cases. If your fixtures do that, your tests become sharper and your debugging gets less stupid.
Start with one dataset for normal paths, then add a smaller set for broken or boundary cases. If you need a fast way to create sample records without hand-writing every field, give the random data generator tool a spin and shape the output to your schema.
That is usually enough to get from brittle fake data to something your API and database can actually trust.