How to Write Test Cases for Two-Factor Authentication (With Examples)
How to Write Test Cases for Two-Factor Authentication (With Examples)
How to Write Test Cases for Two-Factor Authentication (With Examples)
Two‑factor authentication (2FA) adds a second verification step beyond a password, and testing it requires a disciplined approach that covers the happy path, error conditions, timing quirks, and integration points. This guide walks you through the anatomy of a useful test case, shows how to derive positive, negative, and edge‑case scenarios, provides a ready‑to‑use matrix of more than twenty concrete examples, explains how to prepare test data, prioritize effort, and trace each case back to requirements. It also illustrates how manual test design works together with autonomous exploration (e.g., SUSA) to achieve real‑world coverage without maintaining large script suites.
Understanding Two‑Factor Authentication – Core Concepts for Testers
Before writing test cases you need a clear mental model of how 2FA works in the target application. Most implementations follow the pattern: the user enters a primary credential (usually a password), the system validates it, then challenges the user for a second factor. The second factor can be something the user possesses (a phone, a hardware token), something the user is (biometrics), or something the user knows (a PIN). In the answer to a secret question). In practice the most common second factors are:
- SMS‑based one‑time passcode (OTP) – a numeric code sent via a telecom gateway.
- Time‑based One‑Time Password (TOTP) – a 6‑digit code derived from a shared secret and the current Unix time, typically shown in an authenticator app (Google Authenticator, Authy, etc.).
- Push notification – the server sends a push to a registered device; the user approves or denies.
- Email OTP – similar to SMS but delivered via SMTP.
- Hardware token – a USB or NFC device that performs a cryptographic challenge‑response (e.g., YubiKey).
Each factor introduces distinct failure modes: network latency for SMS, clock drift for TOTP, device‑token binding issues for push, spam‑filter delays for email, and physical tampering for hardware. Knowing these helps you anticipate where bugs hide and what data you need to simulate or mock.
Types of 2FA Factors – What to Test
| Factor | Typical API / UI entry point | Success condition | Common failure points |
|---|---|---|---|
| SMS OTP | Input field labeled “Enter code sent to +1‑XXX‑XXX‑XXXX” | Code matches server‑generated value and is within validity window (usually 30‑120 s) | SIM swap, delayed delivery, wrong phone number, rate‑limit exceeded |
| TOTP | Input field labeled “Enter 6‑digit code from authenticator app” | Code equals HMAC‑SHA1(secret, floor(time/30)) mod 10⁶ and is within the current or previous time step | Clock skew >30 s, secret mismatch, reused code, app not installed |
| Push | Modal dialog with “Approve” / “Deny” buttons | User taps Approve within server‑defined timeout (often 60 s) | Push service unreachable, device offline, user denies, spoofed notification |
| Email OTP | Input field labeled “Enter code sent to user@example.com” | Code matches server‑generated value and is within validity window | Email latency, spam filter, incorrect address, inbox full |
| Hardware token | USB/NFC tap or button press followed by PIN entry | Cryptographic response validates against server challenge | Token not present, PIN lockout, firmware bug, USB port issues |
Understanding these rows lets you map each test case to a specific factor and its associated risk.
Anatomy of a Test Case for 2FA
A well‑structured test case contains the following fields, each serving a clear purpose during execution, review, and automation:
- Test Case ID – a unique, immutable identifier (e.g.,
2FA-001). - Title – a short, readable summary (e.g., “Valid SMS OTP leads to successful login”).
- Preconditions – the state that must exist before the first step (e.g., “User is registered, phone number verified, SMS gateway mocked”).
- Test Data – any variables needed (phone number, secret key, OTP length).
- Steps – an ordered list of actions the tester or automation performs.
- Expected Result – the observable outcome after the last step (e.g., “User is redirected to dashboard, session cookie set”).
- Postconditions – state left after the test (e.g., “Session active, no error logs”).
- Priority – often mapped to a risk‑based scale (P1‑critical, P2‑high, P3‑medium).
- Requirement Trace – link to the user story or specification item (e.g.,
US-1234: Enable SMS 2FA).
When you write a test case, keep each field concise but complete enough that another engineer can execute it without guessing. Avoid vague phrasing like “check that the code works”; instead state the exact value or condition you verify.
Example Test Case (Manual)
| Field | Content |
|---|---|
| ID | 2FA-001 |
| Title | Valid SMS OTP leads to successful login |
| Preconditions | 1. User account exists with phone +1‑555‑123‑4567 verified. 2. SMS gateway is in test mode and returns OTP “123456” for any request. 3. User is on the login page, password field filled with correct credential. |
| Test Data | Password = SecurePass!23, Expected OTP = 123456 |
| Steps | 1. Enter password and click Continue. 2. Verify OTP input field appears. 3. Enter 123456 and click Verify. |
| Expected Result | User is redirected to the dashboard page; a session cookie auth_token is set; no error messages displayed. |
| Postconditions | Session is active; subsequent API calls return 200 with user data. |
| Priority | P1 |
| Requirement Trace | US-1234: Enable SMS 2FA |
This layout works equally well for automated scripts; the steps become function calls, and the expected result becomes an assertion.
Positive Test Cases – Happy Path Scenarios
Positive cases confirm that the system behaves correctly when everything is supplied as intended. Below is a table of eight representative happy‑path tests covering the five common factors. Feel free to copy‑paste into your test management tool and adjust IDs to match your numbering scheme.
| ID | Title | Factor | Preconditions | Steps | Expected Result |
|---|---|---|---|---|---|
| 2FA-001 | Valid SMS OTP leads to successful login | SMS | User registered, phone verified, SMS mock returns “654321”. | 1. Login with correct password. 2. Enter OTP “654321”. 3. Submit. | Dashboard loads, session cookie set. |
| 2FA-002 | Valid TOTP from authenticator app authenticates | TOTP | User enrolled with secret JBSWY3DPEHPK3PXP. System time synchronized. | 1. Login with password. 2. Open authenticator, read current 6‑digit code. 3. Enter code and submit. | Access granted, no errors. |
| 2FA-003 | Push notification approval completes login | Push | Device registered, push service reachable, user has app installed. | 1. Login with password. 2. Receive push, tap Approve. 3. Return to app. | Main screen appears, auth token present. |
| 2FA-004 | Email OTP works when inbox is empty | User email verified, mock SMTP returns OTP “987654”. | 1. Login with password. 2. Check inbox for OTP. 3. Enter OTP and submit. | User redirected to profile page. | |
| 2FA-005 | Hardware token (YubiKey) challenge‑response succeeds | Hardware | YubiKey configured for OTP mode, inserted, PIN known. | 1. Login with password. 2. Tap YubiKey when prompted. 3. Enter PIN if required. | Login succeeds, token validation logged. |
| 2FA-006 | Fallback to backup code when primary 2FA unavailable | Backup | User has generated backup codes 111111,222222. Primary 2FA disabled. | 1. Login with password. 2. Choose “Use backup code”. 3. Enter first backup code. | Access granted, backup code marked used. |
| 2FA-007 | Resending SMS OTP within rate limit works | SMS | Mock allows resend after 10 s, limit 5 attempts/min. | 1. Login with password. 2. Click Resend OTP. 3. Wait 12 s, click Resend OTP again. 4. Enter new OTP. | New OTP received, login succeeds after correct entry. |
| 2FA-008 | TOTP window tolerance accepts previous step code | TOTP | Server accepts current and previous time step. | 1. Login with password. 2. Wait until 29 s into a step. 3. Use code from previous step (still valid). 4. Submit. | Login succeeds. |
These cases give you confidence that the nominal flow works for each factor and that auxiliary mechanisms (backup codes, resend, tolerance) are correctly implemented.
Negative Test Cases – Invalid Inputs and Failure Modes
Negative testing proves that the system correctly rejects malformed or missing data and that it gives useful feedback. The following table lists eight negative scenarios, again covering each factor.
| ID | Title | Factor | Preconditions | Steps | Expected Result |
|---|---|---|---|---|---|
| 2FA-009 | Incorrect SMS OTP rejected | SMS | Mock returns OTP “111111”. | 1. Login with password. 2. Enter OTP “222222”. 3. Submit. | Error message “Invalid code”, stay on OTP screen, no session created. |
| 2FA-010 | TOTP rejected after clock drift >30 s | TOTP | User device clock set +45 s ahead of server. | 1. Login with password. 2. Generate TOTP from drifted device. 3. Enter code. | Validation fails, message “Code expired or invalid”. |
| 2FA-011 | Push denial leads to login failure | Push | Push service reachable. | 1. Login with password. 2. Receive push, tap Deny. 3. Observe response. | Login aborted, error “Authentication denied”, user returned to password screen. |
| 2FA-012 | Email OTP not found in inbox results in error | Mock SMTP does not deliver OTP (simulate network failure). | 1. Login with password. 2. Wait for OTP (timeout 30 s). 3. Attempt submit without code. | Inline error “We couldn’t find the code. Please request a new one.” | |
| 2FA-013 | Hardware token missing triggers fallback prompt | Hardware | No YubiKey inserted. | 1. Login with password. 2. System asks for token. | Message “Insert your security key”, focus remains on token prompt, no progress. |
| 2FA-014 | Backup code already used is rejected | Backup | User previously used backup code 111111. | 1. Login with password. 2. Choose backup code. 3. Enter 111111 again. | Error “Backup code already used”, remain on backup screen. |
| 2FA-015 | Exceeding SMS resend limit shows rate‑limit error | SMS | Mock allows 3 resends/min. | 1. Login with password. 2. Click Resend OTP four times within 20 s. | After fourth click, banner “Too many requests. Try again later.” appears, no new OTP sent. |
| 2FA-016 | Empty OTP field submission yields validation error | Any | User on OTP entry screen. | 1. Leave OTP field blank. 2. Click Submit or Verify. | Field‑level error “Code is required”, focus stays on OTP input. |
These cases ensure that error handling is present, that users are guided toward recovery, and that security checks (e.g., replay prevention, rate limiting) are enforced.
Edge and Boundary Cases – Timing, Retry Limits, Clock Drift, etc.
Edge cases often slip through because they involve timing, concurrency, or unusual states that are rare in manual testing but common in production. The table below captures six such scenarios.
| ID | Title | Factor | Preconditions | Steps | Expected Result |
|---|---|---|---|---|---|
| 2FA-017 | OTP submitted exactly at expiration boundary | SMS/TOTP | OTP validity = 30 s. System clock synced. | 1. Request OTP at T0. 2. Wait 29.9 s. 3. Submit OTP. | Accepted (still within window). |
| 2FA-018 | OTP submitted 30.1 s after request (just expired) | SMS/TOTP | Same as above. | 1. Request OTP at T0. 2. Wait 30.1 s. 3. Submit OTP. | Rejected with “Code expired”. |
| 2FA-019 | Concurrent login attempts share same OTP session | SMS | Two browser tabs logged in as same user. | 1. In Tab A request OTP. 2. In Tab B request OTP (same phone). 3. In Tab A enter OTP from step 1. 4. In Tab B attempt to use same OTP. | Only the first validation succeeds; second gets “Invalid or already used code”. |
| 2FA-020 | Device time zone change during TOTP validation | TOTP | User travels across time zones, device clock updates automatically. | 1. Login with password in zone A. 2. Wait until device auto‑adjusts to zone B (offset +2 h). 3. Generate TOTP from new zone and submit. | Validation succeeds because server uses Unix time, not local zone; OTP still valid if within step. |
| 2FA-021 | Push notification delayed beyond server timeout | Push | Mock push service introduces 70 s latency (timeout 60 s). | 1. Login with password. 2. Wait for push (never arrives within timeout). 3. Observe UI. | Timeout error “Push not received. Try again.”, option to resend or use alternative 2FA. |
| 2FA-022 | Hardware token inserted after PIN entry screen appears | Hardware | YubiKey requires PIN before OTP generation. | 1. Login with password. 2. System asks for PIN. 3. Enter correct PIN. 4. Insert YubiKey after PIN screen. 5. Tap token. | Login succeeds; system tolerates slight reordering as long as PIN validated before cryptographic challenge. |
These edge cases test the limits of time windows, replay protection, concurrency handling, and environmental changes that can affect 2FA reliability.
Data Setup and Test Environment Preparation
Reliable 2FA testing hinges on controllable test data. You must be able to generate or mock OTPs, simulate SMS/email delivery, and control device states without relying on real carriers or third‑party services that introduce flakiness.
Generating TOTP Secrets and Codes
A common approach is to use the pyotp library (Python) or its equivalents in other languages. Below is a short script that creates a base32 secret, prints the provisioning URI for QR‑code scanning, and outputs the current and future TOTP values.
import pyotp
import time
# Generate a random secret (store this securely in your test vault)
secret = pyotp.random_base32()
print(f"Secret: {secret}")
# Provisioning URI for authenticator apps (replace with your app name)
uri = pyotp.totp.TOTP(secret).provisioning_uri(name="testuser@example.com",
issuer_name="MyApp")
print(f"Provisioning URI: {uri}")
# Show current and next codes (useful for edge‑case timing)
totp = pyotp.totp.TOTP(secret)
print(f"Now: {totp.now()}")
print(f"In 10s: {totp.at(int(time.time()) + 10)}")
You can persist the secret in a test data file (JSON/YAML) and load it for each test run. For automated suites, wrap the above in a fixture that creates a fresh user account, stores the secret, and tears it down after the test.
Mocking SMS and Email Gateways
Instead of paying for real SMS, use a mock HTTP endpoint that returns a predetermined OTP. Many teams employ tools like MockServer, WireMock, or a simple Express server. Below is a Node.js/Express snippet that mimics an SMS provider:
const express = require('express');
const app = express();
const port = 3000;
// In‑memory store for the latest OTP per phone number
const otpStore = new Map();
app.post('/sms/send', express.json(), (req, res) => {
const { to } = req.body; // expect E.164 format
const code = Math.floor(100000 + Math.random() * 900000).toString();
otpStore.set(to, code);
// Simulate network delay
setTimeout(() => res.json({ status: 'queued', messageId: 'mock-123' }), 200);
});
app.get('/sms/verify', (req, res) => {
const { to, code } = req.query;
const stored = otpStore.get(to);
if (stored && stored === code) {
otpStore.delete(to); // consume OTP
return res.json({ status: 'verified' });
}
res.status(400).json({ status: 'failed', reason: 'invalid_code' });
});
app.listen(port, () => console.log(`Mock SMS listening on :${port}`));
Point your application’s SMS gateway configuration to http://localhost:3000/sms/send for sending and http://localhost:3000/sms/verify for verification. Adjust the logic to simulate latency, failure rates, or rate‑limit responses as needed.
Email mocks can be built similarly using a fake SMTP server (e.g., smtp4dev) or an HTTP endpoint that returns the OTP when queried.
Controlling Device Time for TOTP Drift Tests
When you need to simulate clock skew, you can adjust the system clock in a containerized test or use a library that allows you to inject a custom time source. In Java with JUnit 5, you can use @ExtendWith(MockitoExtension.class) and mock java.time.Clock:
@Test
void totpFailsWhenClockIsAhead() {
Clock fixedClock = Clock.offset(Clock.systemUTC(), Duration.ofSeconds(45));
TOTPGenerator generator = new TOTPGenerator(secret, fixedClock);
String code = generator.generate();
assertFalse(authService.validateCode(code), "Code should be rejected due to drift");
}
For mobile automation (Appium), you can set the device time via ADB:
adb shell date -s "2025-11-02 14:30:00"
Remember to revert the clock after the test to avoid affecting subsequent runs.
Hardware Token Simulation
YubiKey provides a YubiKey Neo with OTP** mode that can be emulated using the yubico-python library in a test harness. For most UI tests, it’s sufficient to mock the backend validation endpoint and simply send a predetermined cryptographic response. This removes the need for physical hardware while still exercising the server‑side logic.
Prioritization and Risk‑Based Ordering
Not all test cases carry equal weight. A practical way to order execution is to plot each case on a risk matrix (Impact vs. Likelihood). High‑impact, high‑likelihood items (e.g., valid SMS OTP leading to account takeover if broken) become P1. Low‑impact, low‑likelihood items (e.g., edge case where a user changes time zone while a push is pending) may be P3.
Below is an example of how you might assign priorities to the cases we listed earlier. Adjust the numbers to match your own risk assessment.
| ID | Impact (1‑5) | Likelihood (1‑5) | Risk Score (Impact×Likelihood) | Priority |
|---|---|---|---|---|
| 2FA-001 | 5 | 5 | 25 | P1 |
| 2FA-002 | 5 | 4 | 20 | P1 |
| 2FA-003 | 4 | 4 | 16 | P2 |
| 2FA-004 | 4 | 3 | 12 | P2 |
| 2FA-005 | 3 | 3 | 9 | P3 |
| 2FA-006 | 4 | 2 | 8 | P3 |
| 2FA-007 | 3 | 4 | 12 | P2 |
| 2FA-008 | 3 | 3 | 9 | P3 |
| 2FA-009 | 5 | 4 | 20 | P1 |
| 2FA-010 | 4 | 3 | 12 | P2 |
| 2FA-011 | 4 | 3 | 12 | P2 |
| 2FA-012 | 3 | 3 | 9 | P3 |
| 2FA-013 | 3 | 2 | 6 | P4 |
| 2FA-014 | 4 | 2 | 8 | P3 |
| 2FA-015 | 3 | 3 | 9 | P3 |
| 2FA-016 | 2 | 4 | 8 | P3 |
| 2FA-017 | 3 | 3 | 9 | P3 |
| 2FA-018 | 3 | 3 | 9 | P3 |
| 2FA-019 | 4 | 3 | 12 | P2 |
| 2FA-020 | 2 | 2 | 4 | P4 |
| 2FA-021 | 3 | 3 | 9 | P3 |
| 2FA-022 | 2 | 2 | 4 | P4 |
How to use the matrix
- Identify regulatory or contractual obligations (e.g., PSD2 requires strong customer authentication; those map to highest impact).
- Consult incident data – if past outages were caused by SMS gateway latency, give those cases higher likelihood.
- Schedule test runs – run all P1 cases on every build, P2 on nightly, P3 weekly, and P4 before major releases.
This risk‑based approach ensures you spend effort where a defect would hurt users or the business most.
Traceability to Requirements and Test Management
Traceability links each test case to a source artifact (user story, specification, regulation). It enables impact analysis when a requirement changes and provides evidence for audits.
Creating a Traceability Matrix
A simple two‑column table works well: the left column lists the requirement ID, the right column lists the test case IDs that verify it. Below is an excerpt for a hypothetical feature set.
| Requirement ID | Description | Verified By Test Cases |
|---|---|---|
| US-1234 | As a user, I can enable SMS‑based 2FA on my account. | 2FA-001, 2FA-009, 2FA-015, 2FA-016 |
| US-1235 | As a user, I can log in using a TOTP from an authenticator app. | 2FA-002, 2FA-010, 2FA-008 |
| US-1236 | As a user, I can approve a login via push notification. | 2FA-003, 2FA-011 |
| US-1237 | As a user, I can receive an OTP via email and use it to log in. | 2FA-004, 2FA-012 |
| US-1238 | As a user, I can use a hardware token (YubiKey) for 2FA. | 2FA-005, 2FA-013 |
| US-1239 | As a user, I can fall back to backup codes when my 2FA device is unavailable. | 2FA-006, 2FA-014 |
| US-1240 | The system must enforce a rate limit on OTP resend attempts. | 2FA-007, 2FA-015 |
| US-1241 | The system must accept TOTP codes from the previous and current time step. | 2FA-008, 2FA-017, 2FA-018 |
| US-1242 | Concurrent login attempts must not reuse the same OTP. | 2FA-019 |
| US-1243 | Push notifications that exceed the server timeout must trigger a fallback error. | 2FA-021 |
| US-1244 | Hardware token PIN entry must be validated before cryptographic challenge. | 2FA-022 |
When you import this matrix into a test management tool (e.g., Zephyr, Xray, TestRail), you can generate reports showing requirement coverage percentage. If a requirement drops below a threshold (say 80 %), you know you need to author additional cases.
Automating Traceability Links
Many frameworks allow you to embed IDs directly in test annotations. In JUnit 5 you can use @Tag:
@Test
@Tag("US-1234")
@Tag("2FA-001")
void validSmsOtpLogsIn() {
// test steps
}
In pytest you can use markers:
@pytest.mark.requirement("US-1234")
@pytest.mark.testcase("2FA-001")
def test_valid_sms_otp():
...
These tags flow into CI pipelines, enabling automatic generation of traceability reports.
Combining Manual Cases with Autonomous Exploration (SUSA)
Manual test cases give you intentional coverage of known scenarios. Autonomous exploration tools complement that by exercising the application in ways a tester might not anticipate—different user personas, random interaction sequences, and edge‑condition triggering.
SUSA (susatest.com) is an autonomous QA platform that, once pointed at an APK or a web URL, explores the app using a set of built‑in personas (curious, impatient, novice, adversarial, elderly, accessibility, power user, etc.). It performs real interactions—taps, scrolls, text entry, handling dialogs—and reports crashes, ANRs, accessibility violations, security issues, and UX friction. Crucially, it also auto‑generates regression scripts (Appium for Android, Playwright for Web) based on the flows it discovers.
How to Run SUSA for a 2FA Flow
- Install the agent
pip install susatest-agent
- Point it at your build (example for an Android APK)
susatest-agent run \
--apk path/to/app-release.apk \
--personas all \
--output-dir ./susa-results \
--timeout 300
The --personas all flag tells SUSA to cycle through its eight built‑in profiles, each with distinct timing, error‑tolerance, and interaction style.
- Review the report
After the run, open ./susa-results/report.html. You will see sections like:
- Crashes – any unhandled exceptions during OTP entry screens.
- ANRs – long‑running UI thread while waiting for SMS gateway simulation.
- Accessibility – missing labels on OTP input fields, insufficient contrast on error messages.
- Security – exposed OTP in logs or network traces.
- UX friction – steps where the impatient persona repeatedly taps the resend button, revealing a missing debounce.
- Leverage auto‑generated scripts
SUSA creates an Appium test suite under ./susa-results/appium/ that you can add to your CI. Because the scripts are derived from actual explorer behavior, they often cover paths you did not manually script, such as a power‑user who pastes a TOTP from the clipboard, or an elderly user who enables large‑font mode and then attempts 2FA.
Benefits of Combining Approaches
| Approach | Strength | Limitation |
|---|---|---|
| Manual test cases | Precise, requirement‑driven, easy to review | Limited to anticipated flows; maintenance overhead |
| Autonomous exploration (SUSA) | Discovers unexpected interaction patterns, persona‑specific issues, regressions without script upkeep | May generate noisy results; needs tuning of personas and timeouts |
By using both, you achieve depth (manual cases verify the spec) and breadth (SUSA explores the state space). For 2FA specifically, SUSA often finds:
- Incorrect handling of whitespace when a curious user pastes a code with leading/trailing spaces.
- Missing error states when an impatient user repeatedly taps “Resend OTP” before the previous request finishes.
- Accessibility gaps when an elderly user
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free