How to Write Test Cases for Two-Factor Authentication (With Examples)

How to Write Test Cases for Two-Factor Authentication (With Examples)

March 13, 2026 · 17 min read · How-To Guides

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:

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

FactorTypical API / UI entry pointSuccess conditionCommon failure points
SMS OTPInput 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
TOTPInput 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 stepClock skew >30 s, secret mismatch, reused code, app not installed
PushModal dialog with “Approve” / “Deny” buttonsUser taps Approve within server‑defined timeout (often 60 s)Push service unreachable, device offline, user denies, spoofed notification
Email OTPInput field labeled “Enter code sent to user@example.com”Code matches server‑generated value and is within validity windowEmail latency, spam filter, incorrect address, inbox full
Hardware tokenUSB/NFC tap or button press followed by PIN entryCryptographic response validates against server challengeToken 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:

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)

FieldContent
ID2FA-001
TitleValid SMS OTP leads to successful login
Preconditions1. 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 DataPassword = SecurePass!23, Expected OTP = 123456
Steps1. Enter password and click Continue.
2. Verify OTP input field appears.
3. Enter 123456 and click Verify.
Expected ResultUser is redirected to the dashboard page; a session cookie auth_token is set; no error messages displayed.
PostconditionsSession is active; subsequent API calls return 200 with user data.
PriorityP1
Requirement TraceUS-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.

IDTitleFactorPreconditionsStepsExpected Result
2FA-001Valid SMS OTP leads to successful loginSMSUser registered, phone verified, SMS mock returns “654321”.1. Login with correct password.
2. Enter OTP “654321”.
3. Submit.
Dashboard loads, session cookie set.
2FA-002Valid TOTP from authenticator app authenticatesTOTPUser 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-003Push notification approval completes loginPushDevice 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-004Email OTP works when inbox is emptyEmailUser 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-005Hardware token (YubiKey) challenge‑response succeedsHardwareYubiKey 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-006Fallback to backup code when primary 2FA unavailableBackupUser 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-007Resending SMS OTP within rate limit worksSMSMock 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-008TOTP window tolerance accepts previous step codeTOTPServer 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.

IDTitleFactorPreconditionsStepsExpected Result
2FA-009Incorrect SMS OTP rejectedSMSMock 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-010TOTP rejected after clock drift >30 sTOTPUser 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-011Push denial leads to login failurePushPush 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-012Email OTP not found in inbox results in errorEmailMock 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-013Hardware token missing triggers fallback promptHardwareNo YubiKey inserted.1. Login with password.
2. System asks for token.
Message “Insert your security key”, focus remains on token prompt, no progress.
2FA-014Backup code already used is rejectedBackupUser 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-015Exceeding SMS resend limit shows rate‑limit errorSMSMock 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-016Empty OTP field submission yields validation errorAnyUser 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.

IDTitleFactorPreconditionsStepsExpected Result
2FA-017OTP submitted exactly at expiration boundarySMS/TOTPOTP validity = 30 s. System clock synced.1. Request OTP at T0.
2. Wait 29.9 s.
3. Submit OTP.
Accepted (still within window).
2FA-018OTP submitted 30.1 s after request (just expired)SMS/TOTPSame as above.1. Request OTP at T0.
2. Wait 30.1 s.
3. Submit OTP.
Rejected with “Code expired”.
2FA-019Concurrent login attempts share same OTP sessionSMSTwo 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-020Device time zone change during TOTP validationTOTPUser 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-021Push notification delayed beyond server timeoutPushMock 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-022Hardware token inserted after PIN entry screen appearsHardwareYubiKey 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.

IDImpact (1‑5)Likelihood (1‑5)Risk Score (Impact×Likelihood)Priority
2FA-0015525P1
2FA-0025420P1
2FA-0034416P2
2FA-0044312P2
2FA-005339P3
2FA-006428P3
2FA-0073412P2
2FA-008339P3
2FA-0095420P1
2FA-0104312P2
2FA-0114312P2
2FA-012339P3
2FA-013326P4
2FA-014428P3
2FA-015339P3
2FA-016248P3
2FA-017339P3
2FA-018339P3
2FA-0194312P2
2FA-020224P4
2FA-021339P3
2FA-022224P4

How to use the matrix

  1. Identify regulatory or contractual obligations (e.g., PSD2 requires strong customer authentication; those map to highest impact).
  2. Consult incident data – if past outages were caused by SMS gateway latency, give those cases higher likelihood.
  3. 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 IDDescriptionVerified By Test Cases
US-1234As a user, I can enable SMS‑based 2FA on my account.2FA-001, 2FA-009, 2FA-015, 2FA-016
US-1235As a user, I can log in using a TOTP from an authenticator app.2FA-002, 2FA-010, 2FA-008
US-1236As a user, I can approve a login via push notification.2FA-003, 2FA-011
US-1237As a user, I can receive an OTP via email and use it to log in.2FA-004, 2FA-012
US-1238As a user, I can use a hardware token (YubiKey) for 2FA.2FA-005, 2FA-013
US-1239As a user, I can fall back to backup codes when my 2FA device is unavailable.2FA-006, 2FA-014
US-1240The system must enforce a rate limit on OTP resend attempts.2FA-007, 2FA-015
US-1241The system must accept TOTP codes from the previous and current time step.2FA-008, 2FA-017, 2FA-018
US-1242Concurrent login attempts must not reuse the same OTP.2FA-019
US-1243Push notifications that exceed the server timeout must trigger a fallback error.2FA-021
US-1244Hardware 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

  1. Install the agent

   pip install susatest-agent
  1. 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.

  1. Review the report

After the run, open ./susa-results/report.html. You will see sections like:

  1. 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

ApproachStrengthLimitation
Manual test casesPrecise, requirement‑driven, easy to reviewLimited to anticipated flows; maintenance overhead
Autonomous exploration (SUSA)Discovers unexpected interaction patterns, persona‑specific issues, regressions without script upkeepMay 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:

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