Developers often use the terms fake data, dummy data, mock data, and synthetic data as if they all mean the same thing.
Sometimes that is harmless.
Sometimes it causes real confusion.
Consider these two records:
{ "name": "Test User", "email": "test@test.com", "city": "Test City" } and:
{ "name": "Anna Schneider", "email": "anna.schneider@example.com", "city": "Hamburg", "country": "DE", "postalCode": "20095" } Both are not real customer records.
But they serve very different purposes.
The first is obviously placeholder data.
The second is designed to resemble the structure and relationships found in real data.
That difference becomes important when you're testing applications, APIs, imports, forms, analytics pipelines, or databases.
So what exactly is synthetic data, and how is it different from ordinary fake data?
What is fake data?
Fake data is any information created specifically for testing, demonstrations, development, or other non-production purposes.
It can be extremely simple:
John Doe 123 Fake Street test@example.com Or even:
foo bar 123 The goal is usually not realism.
The goal is simply to have something to put into a field.
For example, if you're testing whether a form submits correctly, this might be enough:
{ "firstName": "Test", "lastName": "User", "email": "test@example.com" } Nothing about that record needs to resemble a real user.
It only needs to satisfy the basic input requirements.
What is synthetic data?
Synthetic data goes a step further.
It is generated data designed to imitate the properties, structure, distributions, or relationships of real data without copying actual records.
For a simple customer profile, that could mean:
{ "firstName": "Anna", "lastName": "Schneider", "email": "anna.schneider@example.com", "country": "DE", "city": "Hamburg", "postalCode": "20095" } The person is not meant to represent a real customer.
But the record is internally plausible.
The fields relate to each other in a meaningful way.
That makes synthetic data useful for testing systems that behave differently depending on realistic combinations of values.
Fake data can be intentionally unrealistic
Sometimes unrealistic data is exactly what you want.
Suppose you're testing whether a username field accepts a value.
This may be perfectly fine:
test123 You do not need a sophisticated identity generator.
Likewise, if you're testing a calculator:
2 + 2 = 4 there is no benefit in generating realistic financial records.
Simple fake data is often best for:
- unit tests
- simple form tests
- exact business rules
- predictable fixtures
- small examples
- documentation
The simpler the test, the simpler the data should usually be.
Synthetic data is useful when relationships matter
Now imagine a checkout system.
You may need:
name country city postal code phone number currency tax rules shipping options If every field is generated independently, you might get:
{ "country": "DE", "city": "Tokyo", "postalCode": "SW1A 1AA", "phone": "+1 212 555 0100" } Every individual value looks valid.
Together they make little sense.
If your application uses those relationships, your tests become less useful.
Synthetic data can preserve context.
For example:
{ "country": "DE", "city": "Hamburg", "postalCode": "20095", "phone": "+49 40 12345678" } That is much more useful for testing country-aware behavior.
Dummy data is usually simpler
The term dummy data usually refers to placeholder information.
Examples:
John Doe Company ABC 123456 example@example.com It is often used for:
- screenshots
- demos
- temporary UI development
- prototypes
- documentation
Dummy data does not necessarily try to reproduce real-world distributions or relationships.
It just fills space.
That makes it useful.
But it is not automatically realistic.
Mock data is slightly different
Mock data often appears in the context of mocked systems.
For example, a frontend may call an API that does not exist yet.
Instead of making a real request:
const response = await fetch("/api/users"); the frontend might receive a mocked response:
{ "id": 42, "name": "Anna", "plan": "pro" } The important concept is not whether the values are realistic.
The important concept is that the response stands in for another system.
Mocking is about replacing dependencies.
Synthetic data is about generating realistic data.
They can absolutely be used together.
A mocked API can return synthetic data.
Test fixtures are about repeatability
A fixture is data prepared for a test.
For example:
const user = { id: "user-42", plan: "pro", balance: 100 }; Fixtures are often deliberately static.
That is useful because the expected state is obvious.
If a test fails, you know exactly what the input was.
Synthetic data can also be used as a fixture, especially when it is seeded.
For example:
seed = checkout-user-42 The generator can recreate the same synthetic customer every time.
That combines realism with reproducibility.
Realism should match the test
One mistake developers make is assuming that more realistic data is always better.
It isn't.
Suppose you're testing this function:
function addTax(price, rate) { return price + price * rate; } You probably want a test like:
expect(addTax(100, 0.19)).toBe(119); Generating a realistic customer, address, order, product catalog, and payment method adds nothing.
But if you're testing a full checkout flow, realistic synthetic data becomes much more useful.
A simple rule:
Use the least complicated data that still tests the behavior you care about.
Production data is not test data
There is another category developers sometimes use:
copied production data.
For example:
export 1,000 customers import into staging This may create realistic test conditions.
It also creates privacy and security risks.
Production data can contain:
- names
- emails
- addresses
- phone numbers
- account identifiers
- transaction histories
- internal notes
- sensitive metadata
Once copied into development, that information may appear in:
- local databases
- logs
- screenshots
- test exports
- bug reports
- backups
- developer laptops
Synthetic data avoids this problem by generating new records rather than copying actual customers.
Synthetic does not automatically mean anonymous
This distinction is important.
If you take real customer data and simply replace:
John Smith with:
User 123 the dataset is not necessarily synthetic.
Other attributes may still allow the person to be identified.
For example:
location age transaction history rare job title specific timestamp can potentially reveal identity when combined.
Truly synthetic data should be generated rather than merely renaming real individuals.
Format-valid data is another category
Sometimes realism means following a technical format.
For example:
UUID IBAN MAC address IMEI IP address phone number email A useful test value may need to satisfy structural rules.
For example, this:
DE123 is not very useful for testing a real IBAN validator.
You may instead need an identifier that follows:
country length BBAN structure checksum rules That makes the test data technically realistic.
But an important distinction remains:
Format-valid does not mean real-world verified.
A generated IBAN can satisfy structural checks without representing an existing bank account.
A generated device identifier can match the expected format without representing an actual device.
Synthetic data can preserve distributions
For some applications, individual realistic records are not enough.
Imagine testing analytics.
Production data might contain:
70% free users 20% standard users 10% premium users A naive generator could produce:
33% free 33% standard 33% premium That may create misleading results.
A more advanced synthetic dataset could preserve the intended distribution:
free: 70% standard: 20% premium: 10% Likewise, synthetic datasets can model relationships such as:
country → currency country → phone format product → price range subscription → feature usage age group → behavior This becomes useful for:
- analytics
- machine learning testing
- load testing
- dashboards
- data pipelines
- BI systems
Synthetic data can still contain edge cases
Realistic does not mean normal.
Good test datasets should include unusual scenarios.
For example:
very long names empty optional values Unicode characters leap-day dates zero-value orders duplicate records minimum values maximum values unusual postal formats These cases often expose more bugs than ordinary records.
A good synthetic-data system should allow realism and deliberate edge cases to coexist.
Example: signup testing
For a simple unit test, this may be enough:
{ "email": "test@example.com" } For integration testing:
{ "firstName": "Anna", "lastName": "Schneider", "email": "anna.schneider@example.com", "country": "DE" } For edge-case testing:
{ "firstName": "A".repeat(100), "lastName": "Schneider", "email": "anna+very-long-alias@example.com", "country": "DE" } Different tests deserve different data strategies.
Example: API testing
Imagine this endpoint:
POST /customers The API expects:
{ "name": "string", "email": "string", "country": "string", "phone": "string" } A basic fixture could be:
{ "name": "Test", "email": "test@example.com", "country": "DE", "phone": "123" } That may be enough to test JSON parsing.
But it is not very useful for testing phone validation.
A realistic synthetic fixture could be:
{ "name": "Anna Schneider", "email": "anna.schneider@example.com", "country": "DE", "phone": "+49 40 12345678" } Now the same payload exercises more realistic application behavior.
Example: database testing
Suppose you need 100,000 customer rows.
Writing static fixtures is obviously impractical.
A synthetic generator can create:
100,000 records multiple countries different subscription plans varied signup dates different order histories That is useful for:
- query testing
- pagination
- indexing
- search
- analytics
- performance testing
For this kind of workload, synthetic data becomes much more valuable than simple dummy records.
Seeds make synthetic data reproducible
Pure generation introduces a problem:
run 1 → dataset A run 2 → dataset B run 3 → dataset C If dataset A exposes a bug, you may want it again.
That is where seeds help.
For example:
seed = regression-42 can produce the same synthetic dataset repeatedly.
Then a CI failure can report:
Generator: customer Seed: regression-42 A developer can recreate the exact input locally.
This makes synthetic data much more practical for automated testing.
Don't use synthetic data everywhere
Synthetic data is not the answer to every problem.
Use static fixtures when:
- exact values matter
- the test is small
- readability is important
- business rules are simple
Use synthetic data when:
- realistic variation matters
- you need many records
- relationships between fields matter
- you want multiple locales
- you need performance datasets
- production data should not be copied
Use deliberately invalid data when:
- testing validators
- testing error messages
- testing edge cases
- testing defensive code
The best test suite usually combines all three.
A practical hierarchy
I like to think about test data in layers.
Level 1: Placeholder data
foo bar 123 Useful for very simple tests.
Level 2: Static fixtures
{ "email": "test@example.com", "plan": "pro" } Useful for predictable business logic.
Level 3: Synthetic records
{ "name": "Anna Schneider", "email": "anna.schneider@example.com", "country": "DE", "city": "Hamburg" } Useful for realistic application testing.
Level 4: Synthetic datasets
10,000 related records multiple countries weighted distributions edge cases Useful for analytics, system testing, and performance testing.
The higher the level, the more complexity you introduce.
Only move up when the test actually benefits from it.
Where tools like Genory fit
Developer tools such as Genory focus on generating synthetic test data and structured developer fixtures without requiring real customer records.
For example, you can work with:
- synthetic profiles
- custom datasets
- addresses
- UUIDs
- format-based test identifiers
- validators
- developer utilities
For more complex fixture generation, you can start with the Genory test-data tools.
For programmatic workflows, the current API documentation is available at genory.dev/docs.
The goal is not to replace explicit test fixtures.
The goal is to make realistic test data easier to create when realism actually helps.
A simple rule
When choosing test data, ask:
Does this test care about the exact value? If yes:
Use an explicit fixture. If no, ask:
Does realism matter? If no:
Use simple fake data. If yes:
Use synthetic data. And if the test must be reproducible:
Use a seed. Final thought
Fake data and synthetic data are not enemies.
They solve different problems.
Fake data is often simple, obvious, and predictable.
Synthetic data is useful when your tests benefit from realistic structure, relationships, variation, or scale.
The mistake is not choosing one over the other.
The mistake is using complicated data where simple fixtures would work — or using meaningless placeholders when your application depends on realistic relationships.
Choose the data based on the behavior you're trying to test.
Not based on which generator looks more impressive.
