DEV Community

shuo peng
shuo peng

Posted on Fully Autonomous

A Practical Test Matrix for International Checkout Address Forms

Address forms look small, but they sit in the path of checkout, tax calculation, shipping quotes, fraud checks, CRM synchronization and customer support. A form can accept one US address in a happy-path test and still fail as soon as a customer switches to Japan, Hong Kong or Canada.

The problem is usually not a missing regular expression. It is an incorrect data model combined with UI rules that do not change when the country changes.

This article builds a compact test matrix for that boundary. The examples use fictional data and focus on software behavior, not mail delivery or identity verification.

Start with a structured record

Keep the country code and the main address components as separate values. A formatted address is useful for display, but it should not be the only stored representation.

{
  "countryCode": "US",
  "locale": "en-US",
  "addressLines": ["123 Example Street", "Suite 4"],
  "locality": "Seattle",
  "region": "Washington",
  "regionCode": "WA",
  "postalCode": "98101",
  "phone": "+1 206 555 0142",
  "formattedAddress": "123 Example Street, Suite 4, Seattle, WA 98101, USA"
}
Enter fullscreen mode Exit fullscreen mode

The internal name region can represent a state, province, prefecture or district. The UI label can then change without forcing the database schema to change. Google's open-source libaddressinput uses country metadata to determine expected fields, administrative areas and postal-code formats. It is a useful reference even when you do not adopt the library.

Build the matrix around behavior

Avoid one row per country with only “valid address” as the expected result. Use rows that exercise transitions and downstream effects.

Case Setup Action Expected result
Country switch US address is partly filled Change country to Hong Kong State and ZIP requirements are removed; stale US values do not submit silently
Required fields Hong Kong selected Submit without a postal code Form accepts the empty field if the current business rule permits it
Local script Japan selected Enter Japanese locality and street text Unicode survives validation, storage, API output and later rendering
Postal format Canada selected Enter an alphanumeric postal code with a space The value is accepted and preserved as text
Long input A long building and unit description Continue to review Text wraps without hiding content or truncating the stored value
Return navigation A complete address is saved Move forward, then back Every field and the selected country are restored
Downstream update A saved address changes Request shipping again Shipping, tax and delivery estimates are recalculated

These rows describe user-visible behavior. Each can be expanded into unit, integration and end-to-end tests.

Test country switching as a state transition

Country selection should be treated as an input to the rest of the form. Labels, options, required fields, help text and validation constraints are derived state.

const rules = {
  US: {
    regionLabel: "State",
    postalLabel: "ZIP Code",
    postalRequired: true,
    postalPattern: /^\d{5}(?:-\d{4})?$/
  },
  JP: {
    regionLabel: "Prefecture",
    postalLabel: "Postal code",
    postalRequired: true,
    postalPattern: /^\d{3}-?\d{4}$/
  },
  HK: {
    regionLabel: "District",
    postalLabel: "Postal code (optional)",
    postalRequired: false,
    postalPattern: null
  }
};

function applyCountryRule(countryCode, fields) {
  const rule = rules[countryCode];
  fields.region.label = rule.regionLabel;
  fields.postal.label = rule.postalLabel;
  fields.postal.required = rule.postalRequired;
  fields.postal.pattern = rule.postalPattern;
}
Enter fullscreen mode Exit fullscreen mode

The test should change the country after entering values. That catches stale state that a fresh-page test misses.

The following is test-adapter pseudocode; replace the helper methods with those from your browser-testing framework. Fill the new country’s required fields before asserting successful submission.

test("removes the US ZIP requirement after switching to Hong Kong", () => {
  const form = openAddressForm({ country: "US" });
  form.fill({ region: "WA", postalCode: "98101" });

  form.selectCountry("HK");

  expect(form.regionLabel()).toBe("District");
  expect(form.isPostalCodeRequired()).toBe(false);
  form.fill({ street: "1 Example Road", city: "Kowloon", region: "Yau Tsim Mong", postalCode: "" });
  expect(form.submit()).toSucceed();
});
Enter fullscreen mode Exit fullscreen mode

Preserve local text through the whole pipeline

An ASCII-only browser test proves very little. Include local scripts, combining characters, accents, hyphens and long names. Then inspect more than the DOM:

  1. The request payload contains the same Unicode value.
  2. The database stores it without substitution or truncation.
  3. The account page renders it after a reload.
  4. CSV export uses UTF-8 and opens without mojibake.
  5. Receipts, PDFs and email templates wrap the value safely.

The W3C's internationalization quick tips recommend UTF-8 for both forms and servers. The practical QA requirement is to follow the same value from input to every output channel.

Separate format checks from deliverability

A postal-code pattern can tell you whether a value looks plausible. It cannot prove that a building exists or that mail can be delivered there.

Keep these layers explicit:

  • Client format check: catches missing required fields and obvious syntax errors.
  • Server business rule: applies the current checkout or account policy.
  • Address service response: may normalize or suggest an address.
  • Carrier decision: determines serviceability for a shipment.

This separation makes error messages more useful. “Use a five-digit ZIP Code” is actionable. “Invalid address” hides which layer rejected the value.

Assert the stored record, not only the green message

After a successful submission, inspect the structured result.

expect(saved.countryCode).toBe("JP");
expect(saved.locale).toBe("ja-JP");
expect(saved.region).toBe("東京都");
expect(saved.postalCode).toBe("100-0001");
expect(saved.addressLines).toHaveLength(2);
Enter fullscreen mode Exit fullscreen mode

Also test values that should remain empty. An optional postal code accidentally replaced with 00000 is a data-quality bug even if the UI appears successful.

Keep fixtures deterministic

Generated data is useful for exploration, but committed automated tests should use fixed fixtures. A stable set makes failures reproducible and code review understandable.

Use one representative fixture per supported format, then add boundary cases separately:

  • shortest and longest accepted values;
  • optional field omitted;
  • local-script value;
  • punctuation and whitespace;
  • invalid postal syntax;
  • country switch with stale values;
  • downstream service rejection.

The public International Address Form Testing Kit contains a small JSON Schema and fictional JSON/CSV examples that can be copied into a test suite.

A small acceptance checklist

Before releasing an international address form, confirm that:

  • country selection happens before country-dependent fields;
  • required fields and labels update immediately;
  • hidden stale values are cleared or deliberately retained;
  • local text survives save, reload and export;
  • postal codes remain strings, including spaces and leading zeroes;
  • saved addresses trigger new shipping and tax calculations;
  • errors identify the failed field and validation layer;
  • fixtures are clearly fictional and restricted to testing.

I operate US Address Online, a synthetic address test-data tool covering 50 countries and regions. Try the public generator first; use fixed fixtures for regression tests. Generated addresses are not verified mailing addresses. This article was prepared with AI assistance and reviewed against the accompanying demo.

Top comments (0)