PRACTICAL GUIDE / test cases for OTP

Test Cases for OTP: Login, Signup, and Security Examples

Test cases for OTP verification covering expiry, resend, rate limits, invalid codes, delivery failures, device changes, and security controls.

By The Testing AcademyUpdated July 10, 20268 min read
All field guides
In this guide9 sections
  1. Define Purpose, Scope, and Lifetime
  2. Verify Generation and Safe Delivery
  3. Exercise Input and Submission Behavior
  4. Test Expiry With a Controlled Clock
  5. Resolve Resend and Multiple-Code State
  6. Enforce Guess Limits and Abuse Controls
  7. Bind Success to the Correct Workflow
  8. Handle Channel and Service Failures
  9. Make the Flow Accessible and Testable

What you will learn

  • Define Purpose, Scope, and Lifetime
  • Verify Generation and Safe Delivery
  • Exercise Input and Submission Behavior
  • Test Expiry With a Controlled Clock

An OTP can arrive correctly and still authenticate the wrong transaction. That happens when the code is validated only against a phone number or user, while its original purpose, session, amount, or action is ignored. Resends and delayed delivery make the problem harder because several codes may exist at once.

Test the OTP as a short-lived authorization artifact with a defined scope. The visible input is only one part of a system that generates a secret, delivers it through an external channel, limits guesses, consumes it atomically, and advances a specific workflow.

Define Purpose, Scope, and Lifetime

Document whether the code verifies signup contact details, starts a login session, approves a payment, or authorizes a profile change. Record code length and alphabet, lifetime, resend policy, maximum attempts, lockout behavior, supported delivery channels, and what state changes after success.

The OTP should be bound to enough context to prevent reuse in another flow. A payment code may need transaction ID and amount. A login code may need the authentication challenge and intended account. The client must not be able to replace that context while keeping the code.

DimensionBoundary cases
CodeCorrect, wrong, malformed, previously consumed
TimeJust before expiry, at boundary, just after
AttemptsFirst failure, last allowed, one beyond
ResendBefore cooldown, at cooldown, repeated requests
ContextWrong account, session, purpose, or transaction
DeliveryDelayed, duplicate, out of order, unavailable

Verify Generation and Safe Delivery

Request a code for a valid destination, an unknown account where privacy applies, an invalid destination, and a disabled account. Responses should follow the product's enumeration policy. Generation should use a secure source and avoid predictable sequences, but functional tests should focus on uniqueness handling and safe storage rather than trying to prove randomness from a small sample.

Inspect SMS, email, voice, or authenticator delivery through approved test channels. The message should identify the purpose, state the expiry where useful, and avoid unrelated personal data. Logs, analytics, support tools, URLs, and client responses must not contain the raw code.

Test destination formatting, country codes, email casing, delayed provider acknowledgements, rejected destinations, duplicate provider callbacks, and a provider timeout after it may have accepted the message. The UI should describe delivery honestly and offer recovery without generating an uncontrolled stream of codes.

Exercise Input and Submission Behavior

Enter the correct code by typing, pasting, browser or OS autofill, and one-time-code suggestion. Cover empty input, one digit short, one too long, letters in a numeric code, spaces, separators, non-ASCII numerals if unsupported, and a code with leading zero. Leading zeros must never disappear through numeric conversion.

For segmented inputs, verify focus advances on entry, moves correctly on deletion, supports full-code paste, and does not scramble values during rapid input. Screen readers should encounter one understandable code field or a well-labelled group, not six unexplained edit boxes.

Submit twice on a slow connection and press Enter as well as the button. The interface should prevent accidental duplicate requests while the server makes consumption atomic. A successful response should advance only the challenge for which the code was issued.

Test Expiry With a Controlled Clock

Generate a code and redeem it immediately, just before expiry, exactly at the documented boundary, and just after. Define whether the expiry instant is inclusive or exclusive. Server time is authoritative; changing the device clock must not extend validity.

Open the form before expiry and submit after expiry. Background the app until the timer reaches zero, kill and restore it, refresh the browser, and switch zones. The displayed countdown should remain an aid, not the security control. If client and server timers differ, the response should recover cleanly by offering a new request.

Test delayed delivery where the code arrives after its lifetime. The message may be unavoidable, but the expired code must not work. Avoid wording that assures users every delivered code is still valid.

Resolve Resend and Multiple-Code State

Request another code before the cooldown, at the exact cooldown boundary, and immediately after. Confirm the resend control's disabled state matches the server rule. Refreshing or using another device must not bypass the cooldown.

Decide whether a resend invalidates the previous code. Then test both codes in each order, including concurrent submission. If only the newest code is valid, an older delayed message should fail without consuming the new challenge. If multiple codes may remain valid, successful use of one should close the challenge and invalidate the others.

Verify the resend count, expiry timer, attempts remaining, and selected channel stay consistent across tabs and devices. A resend response lost after generation must not make the user unknowingly create many active secrets by repeatedly tapping.

Enforce Guess Limits and Abuse Controls

Submit different wrong codes until the attempt limit. Check the last allowed attempt and one beyond it. The server should count attempts against the challenge or account context, not only the browser. Refreshing, changing device, or replaying the request directly must not reset the counter.

Test rapid generation against one destination, many destinations from one source, and one account from distributed sources in an approved environment. Limits should reduce SMS or email bombing and guessing while giving legitimate users an understandable path to retry later.

Responses should not reveal whether a guessed prefix, account, or expired code was close to valid. Compare invalid, expired, consumed, and wrong-context handling for unnecessary information leaks. Monitoring should capture suspicious velocity without recording codes.

Bind Success to the Correct Workflow

Take a code from signup and attempt it against login, password reset, and another signup. Change the account identifier, challenge ID, payment amount, destination, or callback route while keeping the code. Every cross-context attempt should fail without consuming the valid challenge unless policy explicitly chooses otherwise.

If risk policy binds a challenge to device posture, test a recognized device, a new device, changed browser storage, and a device that fails attestation. The decision should be enforced by the server and recorded without exposing internal risk scores. Legitimate users need an explicit recovery route when device signals are unavailable.

Use the same account on two devices and start two challenges. Verify whether each has an independent code and which session succeeds. A successful login challenge should create one session with the expected assurance level. A verification challenge should mark only the intended email or phone as verified.

After success, replay the request, use browser back, resubmit from another tab, and retry after a network error. One-time consumption should survive retries and service restarts. The next workflow step must validate server state instead of trusting a client-side “verified” flag.

Handle Channel and Service Failures

Simulate provider rejection, rate limiting, timeout, delayed callback, and temporary outage. Distinguish “could not send” from “delivery not yet confirmed.” Offer a retry or alternate channel only when it preserves the same security policy.

If users switch from SMS to email, establish whether the challenge and attempt count continue or restart. Verify masked destination text is accurate but does not expose the full address or number. Changing the destination should require the appropriate earlier authentication step.

When a stronger fallback exists, test movement from OTP to recovery code, authenticator, or support-assisted verification without carrying an already exhausted challenge into an unrelated method. A weaker fallback must not appear merely because delivery is slow. Repeatedly switching channels should preserve abuse counters and should not generate one valid code per channel unless the security model explicitly permits it. Confirm notifications and audit events name the method actually used.

Force the verification service or data store to fail during code consumption. The system must not authorize the action without durable consumption, and it must not consume a code while reporting a retryable failure unless the next attempt can safely discover the completed result.

Make the Flow Accessible and Testable

Associate instructions, errors, countdown, and resend status with the input. Announce errors without moving focus unpredictably. Do not rely on color for expiry or attempt status. Support zoom, large text, password managers, paste, and platform OTP autofill while preventing the code from appearing in ordinary application logs.

For automation, use an approved test-only retrieval seam, fake provider, or captured inbox that is isolated from production. Do not scrape staff messages or query production code stores. Control the clock, challenge IDs, and provider outcomes so expiry and resend races are repeatable.

Maintain a challenge ledger for the decisive cases: correct use, wrong input, expiry boundaries, resend ordering, attempt exhaustion, wrong-context rejection, duplicate submission, and provider failure. Record challenge ID, purpose, attempts, consumption state, and resulting account or transaction state. The ledger confirms that one code authorized one intended action rather than merely satisfying a six-digit form.

// FIELD DISPATCH

Get the QA Field Notes

Weekly QA battles, AI testing guides, and interview drills. Free on Substack.

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 10, 2026 / Reviewed July 10, 2026

PRIMARY REFERENCES

Verify the details at the source

QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.

  1. 01
    Web Security Testing Guide

    OWASP Foundation

    Primary testing scenarios for identity, authorization, input validation, and web security controls.

  2. 02
    OWASP Top 10

    OWASP Foundation

    Current high-level web application security risk taxonomy.

FAQ / QUICK ANSWERS

Questions testers ask

What context must an OTP be bound to?

Bind it to the intended purpose and authoritative challenge, then add account, session, transaction, amount, or destination when those distinguish the action. A signup code must fail against login or password reset, and a payment code must not approve a modified transaction. Client-editable fields cannot define that binding.

How should multiple OTPs behave after a resend?

Document whether resend invalidates the earlier code, then submit old and new codes in both orders and concurrently. If only the newest remains valid, an older delayed message must fail without consuming the current challenge. If several remain valid, the first successful use should close the challenge and invalidate the rest.

How is the exact OTP expiry boundary tested reliably?

Control server time and redeem immediately, just before expiry, at the specified instant, and just after it. Define whether that instant is inclusive. Opening before expiry does not authorize a later submission, and changing the device clock or time zone must not extend validity. The countdown is guidance, not the security control.

Where should failed OTP attempt limits be enforced?

Count attempts against the server-side challenge or account context, not merely the browser. Verify the last allowed guess and one beyond it, then refresh, switch device, and replay the request directly. Channel changes and resend flows should preserve abuse counters unless the security model explicitly defines a stronger replacement challenge.

What is safe behavior if storage fails while an OTP is being consumed?

Never authorize the action without durable one-time consumption. If consumption committed but the response was lost, a retry should discover the completed result without performing it again. If the store failed before commit, return a retryable failure while keeping the workflow unauthorized and the challenge state internally consistent.