Realistic Test Data: Addresses, Phones and Check Digits14 min read2,720 words

Realistic Test Data: Addresses, Phones and Check Digits

Learn how to generate realistic test data for addresses, phone numbers and checksum-protected IDs, so forms expose international edge cases before customers do.

realistic test datainternational address validationphone number testingcheck digit generatorsynthetic test data
Realistic Test Data: Addresses, Phones and Check Digits

Realistic Test Data: Make Your Forms Fail Before Customers Do

A form that accepts 123 Test Street, Testville, 12345 has passed a test. It hasn't necessarily passed a useful one. The data is so familiar and tidy that it hides the kinds of problems you only notice when real customers start typing: a Canadian postal code with letters, an Italian landline that keeps its leading zero, or a customer in the UAE with no postal code to enter.

That gap matters because validation rules often assume one country, one naming convention, or one "ideal" address. Those assumptions make demos look smooth - and can quietly block real users. The fix isn't to throw random characters at every field. It's to generate synthetic data that matches real formats, regional differences, and the messy edge cases your system has to handle - without dragging anyone's personal information into your test environment.

This guide walks through building that data on purpose. You'll model addresses by country and city, test phone numbers in multiple formats, generate identifiers with valid check digits, and set up a repeatable test suite that catches problems beyond the form itself.

Why does realistic test data matter?

Test data is part of your product's test coverage. If it only contains the formats your team sees every day, it can make a fragile system look reliable. A US-only address fixture won't tell you whether a Canadian postal code is rejected by a five-digit regex. A short ASCII street name won't reveal that a database column truncates long names or that an email template drops accented characters.

Usually the problem isn't one "bad regex." It's a chain of assumptions: the form requires a state, the API expects a five-digit postcode, the database uses a narrow field, and a downstream document assumes street and house number are separate. Each component might pass its own basic test - then customer data fails somewhere along the pipeline.

Realistic does not mean personally identifiable

A useful fixture should be structurally realistic but not tied to a real person. That distinction matters. Copying production customer records into your test environment might feel convenient, but it creates privacy and access-control risks. Synthetic data lets you test formats without carrying unnecessary personal information into development, demos, or CI.

It also needs to be realistic for the behavior you're testing. If you're checking whether a postal code field allows letters and spaces, the value needs those characters. If you're testing whether a postcode belongs to a city, a random code with the right shape isn't enough. Your fixture should reflect the relationship your system actually assumes.

Test the assumptions, not just the happy path

Start by listing the decisions your system makes about each field. For an address, that might include whether postal code is required, how a region is represented, and which characters survive storage. For a phone number, it might include whether the input is national or international and how it gets normalized. Then build examples that stress each decision.

For instance, include cases like:

  • A city with a postal code that includes letters and a space, such as M5V 3L9 in Toronto.
  • A country where a postal code may be absent, such as the United Arab Emirates.
  • An address with accents, such as São Paulo or Düsseldorf.
  • A phone number entered in national format and the same number entered with a country code.
  • A long street name that makes it through the database, a PDF, and an email template.

And no, you don't need to test every country in every run. The goal is to make your assumptions visible, then pick cases that can disprove them. A small, deliberately varied suite beats hundreds of random US addresses.

How do you generate realistic international addresses?

First, stop treating an address as one universal tuple of street, city, state, and ZIP code. Those fields exist in some systems, but the meaning and order of address components vary. Some countries use regions; some don't. Some postal codes include letters. Some places don't use postal codes for street delivery at all.

That variation impacts both the UI and the data model. Making every field mandatory can simplify your schema, but it can also make a valid address impossible to submit. A flexible form doesn't have to mean "dozens of country-specific controls." It can also mean not forcing a field's rules where they don't apply.

Model country rules before generating values

A practical generator starts with a small, curated dataset. Store a country, a real city, the applicable region (if any), and the postal-code rules or valid prefixes you want to exercise. Use those patterns as test fixtures - not as a full postal directory.

Examples of differences your data should cover:

  • UK postcodes use letters and digits, with a space between outward and inward parts, like SW1A 2AA.
  • Canadian postal codes follow a letter-digit-letter, space, digit-letter-digit pattern, like K1A 0B1.
  • Irish Eircodes use a seven-character format, often shown with a space, like D02 X285.
  • Brazilian CEPs are eight digits, commonly written with a hyphen, like 01310-100.
  • The UAE generally doesn't use a standard postcode for local delivery, so your postal-code field may need to be optional.

That said, correct formatting doesn't prove an address exists. Rules change, and exceptions happen. If your product makes deliverability decisions, use a maintained address-validation service or authoritative data source. A lightweight test generator shouldn't pretend to be one.

Keep the address internally consistent

Generate the city and region first, then pick a postal-code fixture that matches them. If you generate each field independently, you can end up with a Munich address paired with a code from an unrelated region. It might still match a loose regex, but it's not realistic for a validation flow or a demo that looks "right."

If you need variation where the code structure is known, a pattern filler can help:

function fillPattern(pattern, randomInt) {

return pattern.replace(/[#%@]/g, token => {

if (token === '#') return String(randomInt(0, 9));

if (token === '%') return String(randomInt(1, 9));

return String.fromCharCode(65 + randomInt(0, 25));

});

}

Here, # stands for a digit, % for a non-zero digit, and @ for an uppercase letter. The pattern is just a shape generator. For tests that depend on region-specific validity, use curated known-good examples or valid ranges rather than filling an unconstrained pattern and calling it a postcode.

Test address order and Unicode end to end

Address components aren't always entered or displayed in the same order. Japanese addresses are organized around prefecture, municipality, district, and block numbers, and the display order depends on locale and script. Don't model them like a Western street-and-house-number template and assume "translation" fixes the result.

Also test what happens after submission. Put accented characters and longer-than-usual street or locality names through the full path: browser, API, database, exports, PDFs, and email templates. A field can accept Kraków in the browser and still lose the accent downstream. That's a data integrity bug, not a cosmetic edge case.

How should you test phone numbers and check digits?

Phone numbers fail in a different way: users type the same number in national, international, or E.164 form. A parser that handles one display format but mishandles another can cause failed account verification, duplicate contacts, or broken click-to-call links.

The leading zero is a classic test case. Many countries use a domestic trunk prefix that disappears when dialing internationally. But the rule isn't universal. Italian landline numbers keep the zero after the country code. If your normalizer strips a zero after every international prefix, you'll break valid Italian numbers; if it never strips it, you may mishandle other national formats.

Test phone input and output separately

Use the same underlying number in several representations, then check both parsing and normalization. For example, a UK number can appear nationally as 020 7946 0958, internationally as +44 20 7946 0958, and in E.164 form as +442079460958. Your system should have an explicit policy for accepted input and a consistent canonical representation for storage.

Don't build a global phone parser from a handful of regexes. Numbering plans have country-specific rules, and valid ranges vary. For production parsing, use a maintained numbering-plan library or service. Keep your tests focused on what you actually own: accepted input, stored output, display formatting, and error handling.

For demos and screenshots, pick numbers reserved for fictional use where possible. The US has reserved 555 ranges for fiction, including 555-0100 through 555-0199; Ofcom also reserves UK drama ranges. And use reserved example domains like example.com for test email addresses. That reduces the chance your test contacts a real person.

Generate check digits, not just plausible-looking numbers

Some identifiers include a check digit. Random digits will almost always fail validation immediately, which means you never reach the behavior you intended to test. Generating the check digit lets you create format-valid fixtures while keeping them synthetic.

For a Luhn check digit, compute the checksum from right to left, doubling alternating digits and subtracting nine from any doubled value above nine. The digit that makes the total divisible by ten is the check digit:

function luhnCheckDigit(payload) {

let sum = 0;

let double = true;

for (let i = payload.length - 1; i >= 0; i--) {

let digit = Number(payload[i]);

if (double) {

digit *= 2;

if (digit > 9) digit -= 9;

}

sum += digit;

double = !double;

}

return String((10 - (sum % 10)) % 10);

}

A Luhn-valid value is valid only under that checksum. It doesn't mean a payment card exists, belongs to an account, or can authorize a transaction. For payment integration tests, use your payment provider's official test card numbers and test environment. Don't send generated numbers to a live payment processor.

IBANs use a mod-97 check. You can calculate the check digits by moving the country code and two zeroes to the end, converting letters to numbers, and calculating the remainder. Because the intermediate value can get large, calculate the remainder one digit at a time rather than converting the full string to a JavaScript number. Even then, a passing checksum still doesn't prove the bank account exists or is usable. You also need the correct country-specific BBAN structure.

Match the validation layer to the test

A checksum test answers a narrow question: does this value satisfy a mathematical rule? It doesn't answer whether a card, IBAN, ISBN, or vehicle identifier is issued, active, or tied to a real entity. Keep those claims out of your test names and fixtures. That way, a format-valid synthetic value doesn't get mistaken for verified identity or payment data.

How do you build a safe, repeatable test data workflow?

A generator is only useful if the team can reproduce its output, understand what it guarantees, and run it at the right point in your development process. "Random" shouldn't mean "impossible to debug." If a CI failure appears once in a thousand runs, you need a way to recreate the exact case.

Make randomness fair and failures reproducible

For general UI fixtures, Math.random() might be enough. It's not cryptographically secure, and it's not appropriate for generating secrets. If you need security-grade randomness in a browser, use crypto.getRandomValues(). Even then, taking a random 32-bit number modulo a range can introduce a small bias when the range doesn't divide evenly into 2³².

Rejection sampling avoids that bias by discarding values in the uneven tail:

function randomInt(min, max) {

const range = max - min + 1;

const limit = Math.floor(0x100000000 / range) * range;

const buffer = new Uint32Array(1);

let value;

do {

crypto.getRandomValues(buffer);

value = buffer[0];

} while (value >= limit);

return min + (value % range);

}

For UI fixtures, bias is rarely the biggest risk. Reproducibility is usually more valuable. Accept a seed or save the generated fixture when a test fails, so the same input can be replayed locally. Use secure randomness when unpredictability is actually a security requirement; don't mix that up with ordinary test-data generation.

Build a compact test matrix

You don't need every combination of country, field, and downstream system in every test run. Pick representative cases based on the rules you support and the failures that would matter.

A workable starting matrix might include:

  • A standard US address and phone number.
  • A UK postcode and phone number in national and international formats.
  • A Canadian alphanumeric postcode.
  • An Italian landline with its retained zero.
  • A country where postcode is optional.
  • Accented names and place names, a long street field, and a name with one part.
  • A valid checksum fixture and a near-identical invalid fixture.

Run that matrix through more than the form. Test API validation, database round trips, exports, notifications, and any documents your product creates. The failure often shows up after the form has already accepted the data.

Keep the fixture's promise narrow

Document what each generated value guarantees: format only, checksum validity, city association, or a known reserved example value. Add a matching negative case where it helps prove the validator rejects malformed input. Review country rules periodically - especially if your product uses the data for checkout, shipping, or identity verification.

Conclusion

A form that accepts a tidy placeholder tells you very little about how it will handle real-world data. Better coverage comes from representative synthetic fixtures: varied address conventions, country-aware phone formats, correctly calculated check digits, and values that stay intact across your whole system.

Start with the assumptions your product makes, then build a small test matrix that challenges them. Keep the data synthetic, document what each fixture guarantees, and use official provider test data for payment (or other integrations that touch live systems). This won't perfectly model every country. It will, however, catch the failures your current happy path is hiding.

Frequently Asked Questions

Not by itself. Random strings can expose basic length and character bugs, but they rarely represent relationships between fields or country-specific rules. Use targeted synthetic fixtures for known edge cases, then add randomized input where broad fuzz testing is useful.
No. A Luhn check only confirms that a number satisfies a checksum. It does not make the number an issued payment card or guarantee safe behavior in a live system. Use your payment provider’s official test cards in its test environment, and never submit synthetic numbers to live payment flows.
No. Requirements should follow the countries and address types your product supports. Some countries do not generally use a postcode, and the meaning of region or state varies. Validate required fields in context rather than forcing every user through one country’s address template.
Use a seeded generator or save the exact generated fixture whenever a test fails. Include that fixture in the failure output so it can be replayed locally and in CI. Randomness helps discover cases; reproducibility helps you fix them.

Related articles