A card expiry test checks one narrow thing: whether the expiration date a customer types is formatted correctly, is plausibly in the future, and matches what the issuer says at authorization. That is the whole job. It is a validation test, not a fraud check, and a passing result tells you nothing about whether the card is live. Only an authorization request settles that.
What the expiry field actually does
The expiry date is part of cardholder data under PCI DSS, alongside the PAN, cardholder name, and service code. It is not Sensitive Authentication Data, so it can be stored when there is a business reason, unlike the CVV2. But storing it does not make it a secret. The date is printed on the front of every card and travels in every authorization message, usually as YYMM on the track 2 data per ISO/IEC 7813.
That matters for testing. The expiry is a routing hint and a typo filter, not an authenticator. A form that accepts a date has verified nothing except that the user can type.
The rules your test suite should pin down
I look for four behaviors, in this order:
- Format handling. MM/YY, MM/YYYY, MM-YY, and a bare MMYY should all normalize or fail predictably. A leading zero on the month is optional in most UX, so "1" and "01" should both land as January.
- Month granularity. A card is valid through the last day of its expiry month, not the first. Expiry 09/26 is good until September 30, 2026. Off-by-one-month bugs here are common and quiet.
- Past dates rejected client and server side. Never trust the browser. A disabled Submit button is a UX nicety, not a control.
- Plausible window. Reject dates more than about 20 years out. Typing 12/99 should fail, not sail through as December 2099.
Edge cases that catch real bugs
The current month is the interesting one. Run the test at 23:59 in the customer's timezone, not just UTC, because a server comparing against UTC will reject a card that is still valid where the buyer lives. Two-digit year rollover deserves a dedicated case: 26 means 2026, and your parser should not guess 1926.
Also test garbage: 00/00, 13/26, 00/26, an empty field, spaces, and a pasted "09 / 26" with a space around the slash. Then check what the gateway returns. An expired card usually comes back as ISO 8583 response code 54, and your UI should surface that as "check your expiry date" rather than a generic decline.
One trap worth naming
Do not derive a test expiry from a BIN range. Issuer ranges do not encode expiration, and any fixture that pretends they do will pass in staging and confuse everyone in production. Keep expiry cases in the same table as your PAN and CVC cases, run them in CI on every form change, and log the raw response code so a failure points at the right layer.