How to Test Biometric Login on React Native (Complete Guide)

How to Test Biometric Login on React Native (Complete Guide)

May 23, 2026 · 16 min read · How-To Guides

How to Test Biometric Login on React Native (Complete Guide)

Biometric login has become a standard expectation for mobile users, offering a blend of convenience and security that passwords alone struggle to match. In a React Native app, the biometric flow touches native modules, bridges JavaScript‑to‑native calls, and relies on platform‑specific APIs such as LocalAuthentication on iOS and BiometricPrompt on Android. When any part of this chain misbehaves, users can be locked out, sensitive data can be exposed, or the app can crash silently. This guide walks you through a complete testing strategy—from why biometric login matters, through a detailed test matrix, to manual and automated approaches, edge‑case hunts, accessibility checks, security validation, and finally how autonomous, persona‑driven exploration surfaces bugs that scripted tests often miss.

---

Why Biometric Login Matters in React Native Apps

Security and User Expectations

Users now judge an app’s trustworthiness by how quickly and safely they can gain access. A biometric prompt that fails to appear, shows an incorrect label, or falls back to a password without clear feedback erodes confidence and drives abandonment. From a security standpoint, the biometric flow is often the gatekeeper to token storage, session creation, or payment initiation. If the flow can be bypassed or tricked, an attacker may gain unauthorized access to those downstream services. React Native adds a layer of complexity because the biometric decision is made in native code, yet the result is communicated back to JavaScript through a promise or callback. Any mismatch in handling—such as treating a null response as success—creates a vulnerability window that only appears under specific device states or OS versions.

Common Production Failures

In the wild, biometric login breaks in ways that unit tests rarely catch. A frequent issue is the authentication dialog being dismissed by the system because the app moved to the background while the prompt was visible, leaving the JavaScript side waiting indefinitely for a resolution. Another common failure occurs when a device policy (e.g., an MDM profile) disables biometrics after the app has already cached a biometric‑enabled flag, causing subsequent logins to skip the prompt and fall back to a hardcoded development token. Fingerprint sensors can also return a BIOMETRIC_ERROR_LOCKOUT after too many failed attempts, a state that many React Native wrappers do not surface, leading to silent login failures. Finally, accessibility services such as TalkBack or VoiceOver can interfere with the timing of the native dialog, causing double‑tap gestures to be interpreted as cancel actions. These scenarios only manifest when the app runs on real hardware with varied configurations, underscoring the need for a test matrix that spans happy paths, error paths, and edge conditions.

---

Test Matrix for Biometric Login

Test CategoryDescriptionExpected ResultAutomation FeasibilityNotes
Happy Path – FingerprintValid fingerprint presented, biometric module returns successLogin proceeds, token stored, UI navigates to homeHigh (mock native module)Verify correct handling of promise resolution
Happy Path – Face IDValid face scan, module returns successSame as fingerprintHighEnsure platform branch works
Error – User CancelUser taps cancel button on biometric promptApp shows fallback login screen (PIN/password)Medium (needs UI interaction)Check that cancel error is distinguished from failure
Error – Biometric Not EnrolledNo fingerprint/face registered on devicePrompt to enroll biometrics or show fallbackMediumTest on a clean device or after resetting biometrics
Error – Lockout After Too Many AttemptsFive consecutive failed attempts trigger lockoutApp receives lockout error, shows fallback, does not retry biometric immediatelyLow (requires real sensor)Verify back‑off timing and UI messaging
Error – Sensor UnavailableSensor disabled via device policy or hardware faultPrompt not shown, fallback offered immediatelyLow (needs MDM or simulated fault)Confirm that app does not crash
Edge – App Backgrounded During PromptUser switches to another app while biometric dialog visiblePrompt dismissed, app receives cancellation/error, shows fallbackMedium (needs lifecycle handling)Test with adb shell am pause or Xcode simulator background
Edge – Permission Revoked at RuntimeUser goes to Settings and revokes biometric permission while app is in foregroundNext biometric attempt fails with permission error, fallback shownLow (requires runtime permission toggle)Observe that app does not retain stale success state
Accessibility – TalkBack EnabledTalkBack active, user navigates to biometric button and double‑tapsPrompt announced correctly, login proceeds as usualMediumVerify that announcements are descriptive and not duplicated
Accessibility – Color ContrastBiometric button meets WCAG AA contrast ratioVisual inspection passesLow (manual)Important for low‑vision users
Security – No Biometric Data LeakageAttempt to extract biometric template from app logs or storageNo biometric data present; only opaque result tokenLow (requires forensic tools)Confirm that native module never returns raw data
Security – Replay ResistanceCapture successful biometric response and replay it laterReplay attempt rejected, fallback triggeredLow (needs advanced tooling)Ensure server‑side nonce or challenge is used
Fallback – PIN/Password WorksBiometric fails or is unavailable, user enters correct PIN/passwordLogin succeeds, token issuedHighValidate that fallback path shares same post‑login flow
Fallback – Invalid PIN/PasswordWrong credentials enteredApp shows error, does not proceedHighEnsure lockout timers apply if applicable

*Automation feasibility* reflects how readily the scenario can be exercised in a CI pipeline using mocks, emulators, or device farms. Low feasibility items typically require real hardware or special state setup, making them candidates for periodic manual runs or persona‑driven exploration.

---

Manual Testing Approach Step‑by‑Step

Setting Up Test Devices (iOS/Android)

  1. Provision a clean device or emulator – For iOS, use a device with Touch ID/Face ID enabled and no existing biometric enrollment for the test account. For Android, ensure the device runs API 28+ with a fingerprint sensor or use the emulator’s fingerprint simulation (adb -e emu finger touch ).
  2. Install the debug build – Use npx react-native run-ios --device or npx react-native run-android to load the latest code.
  3. Enable developer logging – Add console.log statements around the biometric call or use Flipper to capture native bridge traffic.

Enrolling Biometrics

Executing Test Cases

Follow the matrix above, marking each step with a pass/fail. For UI‑driven steps (cancel, fallback), use the device’s physical buttons or on‑screen prompts. Keep a notebook of observations:

Observing Logs and Crash Reports

---

Automated Testing with React Native Specific Tools

Unit Tests with Jest and React Native Testing Library

Unit tests validate the JavaScript layer that prepares data for the biometric call and interprets the result. Mock the native module so you can control its resolution.


// __mocks__/react-native-touch-id.js
export default {
  authenticate: jest.fn(),
  isSupported: jest.fn(),
};

import TouchID from 'react-native-touch-id';
import { loginWithBiometrics } from '../src/auth';

describe('loginWithBiometrics', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('successful fingerprint leads to navigation', async () => {
    TouchID.authenticate.mockResolvedValueOnce({ success: true });
    const navigation = jest.fn();
    await loginWithBiometrics(navigation);
    expect(TouchID.authenticate).toHaveBeenCalled();
    expect(navigation).toHaveBeenCalledWith('Home');
  });

  test('user cancel triggers fallback', async () => {
    TouchID.authenticate.mockRejectedValueOnce({
      code: LAError.userCancel,
    });
    const fallback = jest.fn();
    await loginWithBiometrics(null, fallback);
    expect(fallback).toHaveBeenCalled();
  });
});

Run with npm test. This guarantees that promise handling, error branching, and state updates stay correct after refactors.

End‑to‑End with Detox

Detox drives the actual native UI, allowing you to test the biometric prompt interaction on simulators or real devices. Because the prompt is a system dialog, Detox cannot interact with it directly, but you can simulate the native response using the detox device APIs.


// e2e/biometric.login.spec.js
describe('Biometric login flow', () => {
  beforeEach(async () => {
    await device.launchApp({ newInstance: true });
  });

  it('should succeed with mocked fingerprint', async () => {
    // Simulate successful fingerprint on Android emulator
    await device.sendToApp({ 
      type: 'fingerprint', 
      fingerPrintId: 1, 
      success: true 
    });
    await expect(element(by.id('homeScreen'))).toBeVisible();
  });

  it('should show fallback after cancel', async () => {
    await device.sendToApp({ 
      type: 'fingerprint', 
      fingerPrintId: 1, 
      success: false, 
      cancel: true 
    });
    await expect(element(by.id('loginWithPin'))).toBeVisible();
  });
});

For iOS, use device.sendToApp({ type: 'touchId', match: true }) or match: false to emulate success/failure.

Mocking expo-local-authentication

If your project uses Expo, mock the module in Jest:


// __mocks__/expo-local-authentication.js
export default {
  authenticate: jest.fn(),
  hasHardwareAsync: jest.fn().mockResolvedValue(true),
  isEnrolledAsync: jest.fn().mockResolvedValue(true),
};

Then write tests similar to the TouchID example.

CI Integration

Add a step in your CI (GitHub Actions, Bitrise, etc.) that runs the Detox suite on a device farm (Firebase Test Lab, AWS Device Farm). For the low‑feasibility edge cases (lockout, sensor disabled), schedule a weekly manual run or leverage a tool that can inject device policy changes via MDM APIs.

---

Edge Cases and Production‑Only Bugs

Biometric Lockout After Too Many Attempts

When the native sensor reaches its lockout threshold, the biometric module returns a specific error (BIOMETRIC_ERROR_LOCKOUT on Android, LAError.biometryLockout on iOS). Many React Native wrappers treat any non‑success as a generic failure, causing the app to immediately retry the biometric prompt, which frustrates users and may trigger a permanent lockout.

Test:

  1. Enroll a fingerprint.
  2. Fail authentication five times using an unregistered finger.
  3. On the sixth attempt, assert that the error object contains a lockout code and that the UI shows a message like “Try again in 30 seconds” or falls back to PIN.

Device Policy Changes (e.g., Admin Disabling Biometrics)

Enterprise MDM solutions can remotely disable biometrics. If your app caches a biometricsEnabled flag at startup, it will continue to attempt biometric auth after the policy change, leading to confusion.

Test:

Authentication Dialog Dismissal Timing

If the app moves to the background while the system biometric dialog is visible, iOS dismisses the prompt and returns LAError.userCancel. Android behaves similarly but may also return BIOMETRIC_ERROR_CANCEL. If your JavaScript does not distinguish cancel from failure, you might incorrectly treat it as a credential error and lock the user out.

Test:

Background/Foreground Transition

Some apps perform token refresh in the background. If a biometric prompt is still pending when the app returns to the foreground, the native module may deliver a stale result, causing a race condition.

Test:

Permission Revocation at Runtime

Both platforms allow users to revoke biometric permission while an app is in the foreground. If the app does not re‑check permission before invoking the auth method, it may crash with a security exception.

Test:

Accessibility Overlays (TalkBack, VoiceOver)

Screen readers can inject additional touch events or change the timing of gestures. A double‑tap that should confirm biometric auth might be interpreted as a cancel if the accessibility service intercepts the event.

Test:

Biometric Sensor Failure (Dirty Finger, Poor Lighting)

Real‑world sensors can return transient errors like BIOMETRIC_ERROR_SENSOR_NOT_DETECTED or LAError.biometryNotAvailable. If your app treats these as permanent failures, users may be forced into fallback unnecessarily.

Test:

---

Accessibility and WCAG Considerations

Labeling Biometric Prompt

The native biometric dialog already includes a system‑provided reason string (passed via localAuthenticate or BiometricPrompt.Builder.setDescription). Ensure that this string is concise, localized, and does not rely on color alone to convey meaning.

Check:

Alternative Login Paths

WCAG 2.1 Guideline 1.3.1 requires that information and relationships conveyed through presentation can be determined programmatically. If your app only offers biometric login, users who cannot use biometrics are blocked. Provide a visible, labeled alternative (PIN, password, or OTP) that is reachable via the same navigation hierarchy.

Test:

Screen Reader Announcements

When the biometric prompt appears, the screen reader should announce something like “Place your finger on the sensor to sign in” or “Look at the phone to unlock”. If your app overrides the default prompt with a custom modal, you must manually set accessibility labels.

Test:

---

Security and Privacy Testing

Ensuring No Biometric Data Leakage

Biometric templates never leave the Secure Enclave (iOS) or Trusted Execution Environment (Android). However, developers sometimes mistakenly log the result object or store it in AsyncStorage.

Test:

Testing Fallback to PIN/Password

A secure biometric flow must gracefully degrade to a knowledge‑based factor when biometrics are unavailable or rejected. Verify that the fallback uses the same session‑management logic (e.g., issues a JWT after validation) and that the token is stored with the same security level (Keychain/Keystore).

Test:

Secure Storage of Auth Tokens

After biometric validation, the app typically receives a session token from the backend. Ensure that this token is not written to plain‑text files or shared via console.log.

Test:

Resistance to Replay Attacks

If the backend only checks a static success flag from the biometric module, an attacker could replay a previously captured success response.

Test:

---

Autonomous, Persona‑Driven Exploration with SUSATest

How SUSA Models Different User Personas

SUSATest’s autonomous agent loads the APK (or points at a web URL) and then drives the app using a set of behavior profiles. Each profile—curious, impatient, novice, adversarial, elderly, accessibility‑focused, power user—applies distinct timing, tap patterns, and error‑recovery tendencies. For biometric login, the agent will:

What It Discovers That Scripts Miss

Scripted tests follow a predetermined sequence and often assume ideal conditions (device unlocked, sensor clean, no background interruptions). SUSATest’s exploratory nature surfaces issues such as:

Example Session Log

Below is a condensed excerpt from a SUSATest run on a sample React Native app that uses react-native-touch-id. The log shows the agent’s actions, the observed native responses, and the resulting verdict.


[Persona: Curious] 08:12:03 - Launched app, navigated to LoginScreen
[Persona: Curious] 08:12:05 - Tapped BiometricButton
[Native]   BiometricPrompt shown (reason: "Sign in to ExampleApp")
[Persona: Curious] 08:12:07 - Waited 2s, then swiped up to home (backgrounded)
[Native]   Prompt dismissed, returned LAError.userCancel
[Persona: Curious] 08:12:09 - App displayed fallback PIN screen (PASS)
[Persona: Impatient] 08:13:41 - Tapped BiometricButton, waited 0.3s, tapped again
[Native]   Second call received while first pending → returned LAError.systemCancel
[Persona: Impatient] 08:13:43 - App showed generic error dialog, no fallback (FAIL)
[Persona: Elderly]   08:15:20 - Tapped BiometricButton, waited 5s (slow)
[Native]   Prompt auto‑canceled after 30s? Actually returned LAError.userCancel after 5s due to system timeout? (Check)
[Persona: Elderly]   08:15:26 - App displayed fallback after timeout (PASS)
[Persona: Accessibility] 08:16:10 - TalkBack enabled, double‑tapped BiometricButton
[Native]   Prompt announced, user placed finger, succeeded
[Persona: Accessibility] 08:16:13 - Login succeeded, home screen reached (PASS)

The log reveals that the Impatient persona uncovered a bug where rapid double‑taps caused a system‑cancel error that the app did not handle, leading to a missing fallback. This defect would likely stay hidden in a scripted test that always waits a fixed interval before proceeding.

---

Checklist for Biometric Login Testing

✅ ItemDescriptionHow to Verify
Biometric prompt shows correct reason stringReason is localized and readableListen with TalkBack/VoiceOver, inspect UI
Success path leads to intended screenAfter valid biometric, app navigates to home or protected areaObserve navigation, check token storage
Cancel triggers fallback loginTapping cancel shows PIN/password optionTap cancel, verify fallback UI
Biometric not enrolled shows enrollment prompt or fallbackNo biometric saved → system asks to enroll or offers alternativeClear enrollment, attempt login
Lockout after too many failures shows appropriate messageAfter N failed attempts, app does not retry biometric immediatelyFail N+1 times, check UI and timing
Sensor unavailable (policy/hardware) skips promptWhen biometrics disabled by MDM or sensor fault, fallback appears immediatelyDisable biometrics via settings, test login
App backgrounds during prompt handled gracefullySwitching away while prompt visible results in cancel/error and fallbackPress home button during prompt, observe outcome
Permission revoked at runtime leads to fallbackUser turns off biometric permission while app in foregroundRevoke permission, tap biometric button
Accessibility services do not break flowTalkBack/VoiceOver active, biometric login worksEnable service, complete login
No biometric data leaked in logs/storageNo raw fingerprint/face data appears in logs, AsyncStorage, or shared prefsSearch logs and storage for biometric keywords
Fallback to PIN/password uses same auth backendToken issued after fallback matches token after biometric successCompare network payloads, verify endpoint
Rate limiting / backoff applied after repeated failuresAfter many failed PIN attempts, app enforces delay or lockoutAttempt wrong PIN repeatedly, measure delay
Replay attack resistedReplaying a captured biometric success is rejected by backendCapture success, resend after delay, check server response
Secure storage of post‑login tokenToken stored in Keychain/Keystore, not plain‑text filesInspect file system for token copies
Manual exploratory runs catch persona‑specific bugsRunning SUSATest or similar yields new failures not in script suiteExecute autonomous agent, review new FAIL entries

---

Closing Takeaways

Biometric login is a high‑impact feature that blends native security with JavaScript‑driven UI. In React Native, the surface area for failure is wide: the bridge, the native module, platform‑specific error codes, lifecycle events, accessibility overlays, and enterprise policies all influence whether a user can log in securely and quickly.

A thorough strategy begins with a clear test matrix that separates happy paths from error conditions and edge cases, then validates each layer—unit tests for decision logic, Detox‑driven E2E for UI interactions, and manual checks for states that are hard to emulate (lockouts, policy changes, sensor obstructions). Accessibility and security tests are not optional; they protect users with diverse needs and ensure that biometric data never leaves the secure enclave.

Finally, autonomous, persona‑driven exploration complements scripted testing by exercising the app in ways real users do: varying timing, mixing accessibility gestures, simulating sensor noise, and probing for race conditions that only appear under unusual but plausible usage patterns. Tools like SUSATest surface these hidden defects early, reducing the chance that a biometric‑related bug reaches production and erodes trust.

By combining the matrix, layered automation, manual diligence, and exploratory agents, you can ship a biometric login experience that feels seamless, works for every user, and resists both accidental failure and malicious abuse.

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