β€’
Drizz raises $2.7M in seed funding β€’
β€’
Featured on Forbes
β€’
Drizz raises $2.7M in seed funding β€’
β€’
Featured on Forbes
Logo
Blog page
>
How to Test Biometric Authentication in Mobile Apps

How to Test Biometric Authentication in Mobile Apps

Biometric authentication testing for mobile apps. Real error codes, key invalidation scenarios, iOS and Android API differences, and testing gaps.
Author:
Partha Sarathi Mohanty
Posted on:
July 3, 2026
Read time:

Key takeaways

  • Biometric authentication testing is not about testing "Does Face ID unlock app." It's about testing every failure mode, every fallback path, every key invalidation trigger, and every error state OS can throw at you.
  • Real biometric bugs live in edge cases: user enrols a new fingerprint (key invalidated), user disables Face ID from Settings (silent failure), there are too many failed attempts (system lockout), or biometric hardware is unavailable (missing fallback).
  • Emulators and simulators lie. They can simulate success or failure but not hardware attestation, secure enclave behaviour, or real error codes your users will hit. Real devices are required for anything security-critical.

Biometric authentication testing on mobile is the practice of verifying that your app handles biometric prompts, successes, failures, fallbacks, key invalidations, and lockout states correctly on both iOS and Android. Most teams test happy path (user opens app, Face ID succeeds, user is in) and stop. That's testing 15% of surface. The other 85% is where security bugs and one-star reviews come from.

Getting this right matters because biometric authentication is now primary login path for banking apps, payment apps, health apps, and increasingly for consumer social apps. A biometric bug isn't a login inconvenience. It's a locked-out user or, worse, an incorrectly authenticated one.

What biometric authentication actually does under hood

Before you test it, you need to know what it is. Every generic article skips this and jumps straight to "Test Face ID and fingerprint." You can't meaningfully test what you don't understand.

Biometric authentication on mobile is a hardware-backed identity check. The user's biometric data (fingerprint template, face embedding, and iris scan) is stored in a dedicated secure hardware component: Secure Enclave on iOS, StrongBox or Trusted Execution Environment (TEE) on Android. Your app never sees biometric data. Your app calls an API, OS handles biometric check with hardware, and OS tells your app "authenticated" or "not authenticated".

There's a second layer that matters for security-critical apps. Cryptographic keys can be created that are only usable after biometric authentication. When you decrypt something with a biometric-protected key, OS enforces a biometric check as part of the crypto operation. This is different from a boolean "did user auth" check because a key literally can't work without a valid biometric. That's difference between authentication (proving identity) and authorisation (allowing an operation).

Testing biometric authentication means testing both layers: "was user authenticated" flow and the "does app correctly use biometric-protected keys" flow. Most teams test first. Almost none test second.

Once password login works, biometric authentication testing is natural next layer of your auth test coverage. It doesn't replace password testing. It adds to it, because biometric flows always have a password fallback somewhere.

iOS biometric authentication testing: what you're actually testing

On iOS you're calling LocalAuthentication framework. Most of it goes through LAContext.

The basic flow:

  1. Your app creates an LAContext instance.
  2. Your app calls canEvaluatePolicy(_:error:) with either . deviceOwnerAuthenticationWithBiometrics (biometric only) or .deviceOwnerAuthentication (biometric with passcode fallback).
  3. If that returns true, your app calls evaluatePolicy(_:localizedReason:reply:) to actually trigger prompt.
  4. The OS shows a biometric UI (Face ID camera, Touch ID sensor, or Optic ID on Vision Pro).
  5. The reply closure fires with either success or an LAError.

The LAError codes you need to test against are documented in Apple's LocalAuthentication error reference. Face ID and Touch ID coverage falls under biometric authentication testing on iOS, and Touch ID specifically is now legacy testing (only iPhone SE and older iPads still have it), but you need it if you claim to support those devices. The LAError codes you actually hit in production:

  • .authenticationFailed (-1). User was scanned but didn't match. Face didn't align, the finger was wrong, etc.
  • .userCancel (-2). User tapped Cancel button.
  • .userFallback (-3). User tapped "Enter Password" or the fallback button.
  • .systemCancel (-4). OS cancelled (app went to background, screen locked, etc.).
  • .biometryNotAvailable (-6). Hardware not present or unavailable. Rare on modern iPhones but possible.
  • .biometryNotEnrolled (-7). User hasn't set up biometric data at all.
  • .biometryLockout (-8). Too many failed attempts. User has to enter passcode to unlock biometric.

You should have a test case for every one of these. Not "we handle failures gracefully." A test that specifically triggers each error and verifies correct fallback UI appears.

Then there's Keychain access control. If your app stores anything in Keychain protected by biometrics (session tokens, encryption keys), you're using SecAccessControlCreateWithFlags with flags like .userPresence, .biometryAny, or .biometryCurrentSet. That last one is interesting one: .biometryCurrentSet invalidates stored item if user changes their enrolled biometrics. A user enrolls a new fingerprint, and your Keychain item is now unreadable. Test that. Your app should handle it by re-authenticating and re-storing item, not crashing or showing a broken state.

Android biometric authentication testing: different beast, same problems

Android's story is messier because it evolved. FingerprintManager was deprecated. BiometricPrompt is current recommended API. BiometricManager gives you capability checks. Underneath, hardware-backed keys use AndroidKeyStore with either StrongBox or TEE.

The authenticator classes matter. When you build a BiometricPrompt.PromptInfo, you specify setAllowedAuthenticators(...) with combinations of:

  • BIOMETRIC_STRONG (Class 3). Hardware-backed, meets Android's Class 3 security requirements. Can be used to unlock keys.
  • BIOMETRIC_WEAK (Class 2). Lower assurance. Can't be used to unlock cryptographic keys.
  • DEVICE_CREDENTIAL. PIN, pattern, or password fallback.

Testing this correctly means verifying that if your app requires BIOMETRIC_STRONG for a payment authorization, it doesn't accept BIOMETRIC_WEAK because user's phone only has face unlock (which is weak on most Android devices). Google's device compatibility varies wildly here. A Pixel 8's face unlock is Class 3. A Samsung Galaxy A54's face unlock is Class 2. Your app needs to know difference and behave correctly.

The error codes in BiometricPrompt.AuthenticationCallback:

  • ERROR_HW_UNAVAILABLE (1). Sensor not available (rare).
  • ERROR_UNABLE_TO_PROCESS (2). Sensor couldn't process input.
  • ERROR_TIMEOUT (3). User took too long.
  • ERROR_NO_SPACE (4). Not enough storage to save biometric-related data.
  • ERROR_CANCELED (5). Canceled by OS.
  • ERROR_LOCKOUT (7). Too many failed attempts. Temporary lockout.
  • ERROR_LOCKOUT_PERMANENT (9). Too many failed. User needs to unlock device with credential.
  • ERROR_USER_CANCELED (10). User tapped cancel.
  • ERROR_NO_BIOMETRICS (11). No biometrics enrolled.
  • ERROR_HW_NOT_PRESENT (12). Hardware not present.
  • ERROR_NEGATIVE_BUTTON (13). User tapped fallback button.
  • ERROR_NO_DEVICE_CREDENTIAL (14). Device isn't secured with lock screen.

Same drill as iOS. Every one of these needs a test case that specifically triggers it and verifies app response. Google's official BiometricPrompt documentation has full sample code and callback signatures if you're wiring this up from scratch.

The key invalidation problem also exists on Android. When you create a KeyStore key with setUserAuthenticationRequired(true) and setInvalidatedByBiometricEnrollment(true), key becomes unusable if user adds or removes a biometric. Same failure mode as iOS .biometryCurrentSet. Same testing requirement.

Biometric authentication testing sits inside a broader security testing scope, and how you handle these hardware-backed key invalidations is what auditors will actually look at during a security review.

The states you need to test (real list)

Generic articles list "test success, test failure, test cancel." That's three tests. Here's actual list of states you need to cover.

Setup states:

  1. Biometric hardware present, user has enrolled biometrics, biometric enabled for your app.
  2. Biometric hardware present, user has enrolled biometrics, biometric disabled by user in Settings.
  3. Biometric hardware present, user has NOT enrolled biometrics.
  4. Biometric hardware present but disabled at OS level.
  5. Biometric hardware not present (older Android device, iPad without Touch ID).

Authentication attempt states:

  1. Prompt shown, user authenticates successfully.
  2. Prompt shown, user cancels.
  3. Prompt shown, user taps fallback (password).
  4. Prompt shown, user authentication fails once, retries and succeeds.
  5. Prompt shown, user fails N times (varies: 5 on iOS, 5 on Android typically) and hits temporary lockout.
  6. Prompt shown, user hits permanent lockout (Android specific).

Environmental states:

  1. App backgrounded while prompt is showing. Test that prompt cancels correctly, that resume state is sane, and that user isn't left in an in-between state.
  2. Screen locked while prompt is showing.
  3. Incoming call while prompt is showing.
  4. Wi-Fi drops during a biometric-gated operation (payment authorization).

Enrollment change states:

  1. User adds a new fingerprint after your app stored a .biometryCurrentSet Keychain item. Verify item is invalidated and re-registration flow triggers.
  2. User removes their only enrolled biometric. Verify your app degrades gracefully.
  3. User adds Face ID on a device that previously only had Touch ID (upgrade path).

Cross-platform states:

  1. Android: BIOMETRIC_STRONG required but device only offers BIOMETRIC_WEAK. Verify correct fallback.
  2. Android: user enables device credential fallback but disables biometrics. Verify prompt still works.
  3. iOS: deviceOwnerAuthentication policy vs deviceOwnerAuthenticationWithBiometrics. Test both.

That's 21 test cases minimum. Not one. Not three. Twenty-one. And that's before you multiply by device matrix.

Emulators and simulators lie

Every article that recommends running biometric tests on simulators is misleading you.

Xcode Simulator lets you toggle Face ID / Touch ID behavior via Features menu: Enrolled, Matching Face, Non-matching Face. That's useful for basic flow testing. But Simulator doesn't actually run Secure Enclave. Your keys aren't hardware-backed. Your .biometryCurrentSet flag doesn't behave same as on a real device because there's no real biometric enrollment to compare against. Attestation calls don't work.

Android emulator has similar limitations. You can go to Settings β†’ Security β†’ Biometrics and set up a fingerprint, then use adb -e emu finger touch <finger_id> to simulate a touch. That triggers callback. But hasStrongBox() returns false in most emulator configurations because there's no StrongBox hardware. Class 3 vs Class 2 behavior isn't reliably simulated.

For anything that involves cryptographic operations, attestation, or real distinction between BIOMETRIC_STRONG and BIOMETRIC_WEAK, you need real devices. This is a hard requirement for security-critical apps: banking, payments, health, government.

The practical implication for your test strategy: keep emulator/simulator tests for happy path and basic error handling, but require real devices for security-critical scenarios and enrollment change tests. This split roughly matches "unit and component tests on emulators, integration and E2E on real devices."

Key invalidation is where security bugs ship

This is section I want you to actually take away.

The key invalidation flow is: user enrolls a new biometric (adds a fingerprint, changes their Face ID) β†’ OS invalidates all biometric-protected keys that were created with invalidatedByBiometricEnrollment flag (Android) or .biometryCurrentSet access control (iOS) β†’ your app tries to use those keys β†’ they fail β†’ your app needs to handle this and re-register key.

Most apps don't test this. Most apps also don't handle it correctly. The failure modes we see in production:

  • App shows "Something went wrong" and gives no way forward. User uninstalls.
  • App silently falls back to password login without telling user why biometrics stopped working.
  • App logs user out completely and requires re-registration from scratch (technically secure but bad UX).
  • App keeps prompting for biometric and failing, in a loop.

The correct behavior is: detect key invalidation, tell user "we noticed you updated your biometric settings, please authenticate with your password to re-enable biometric login," verify password, re-create biometric key, log user in. That's 4 steps. Nobody tests all 4.

To test this on a real iOS device:

  1. Install your app.
  2. Register biometric auth in your app.
  3. Do something that uses biometric-protected Keychain item.
  4. Go to Settings β†’ Face ID (or Touch ID) & Passcode.
  5. Add a new face (Alternate Appearance) or fingerprint.
  6. Return to your app.
  7. Try to use biometric-protected feature.
  8. Verify correct re-registration flow.

On Android: same idea, but you add a fingerprint via Settings β†’ Security β†’ Fingerprint, then verify.

This test is manual today for most teams because framework support for automating "add a new biometric to OS" is essentially nonexistent. You can't script this through Appium. You do it by hand once per release on primary devices in your matrix. Ten minutes of real testing catches bugs that would otherwise ship.

Rate limiting and lockout

Both platforms lock out biometric authentication after too many failed attempts. iOS locks after 5 failed Face ID attempts, then requires passcode to unlock biometric. Android has similar behavior with ERROR_LOCKOUT (temporary, unlocks after 30 seconds) and ERROR_LOCKOUT_PERMANENT (needs credential to unlock).

Your app should:

  • Show a helpful message when lockout happens ("Face ID is temporarily locked, please enter your password").
  • Not just show raw error code or "Authentication failed."
  • Correctly fall back to password flow.
  • Not trigger a support ticket flood because users think your app is broken.

Testing this requires actually failing biometric N times in a row. On a real iPhone, that means presenting your face at an angle that Face ID won't match, 5 times. Or covering sensor. Or entering someone else's face. This is genuinely tedious manual testing but you should do it once per major release.

Where selector-based automation dies here

For general app testing, selector-based tools (Appium, XCUITest, Espresso) are already fragile. For biometric testing, they mostly can't do job at all.

The biometric prompt is a system dialog rendered by OS, not by your app. Its accessibility hierarchy is limited, its buttons don't have consistent IDs across OS versions, and simulating a biometric event requires either a virtual sensor (emulator/simulator only) or a physical action (real device, no automation). Appium supports biometric enrollment simulation on virtual devices only. On real devices, you're back to manual testing.

BrowserStack, Sauce Labs, and TestGrid all offer platform-specific biometric simulation features that hook into their real device infrastructure. Those work but tie you to their platform, and they don't fully test hardware-backed key flows because their simulation is at OS API level, not actual Secure Enclave / StrongBox.

The upshot: automated biometric testing is limited. Manual testing is honest gap-filler for flows that matter most.

What works for biometric authentication testing at scale

The biometric prompt itself might not be automatable end-to-end. Everything around it (flow that leads up to it, fallback flow after cancel, error message after failure, state after key invalidation) can and should be automated.

Drizz tests are written as plain English commands. Tap "Log in with Face ID". Validate "Face ID is locked. Enter password" is visible. Vision AI reads screen way a person would, so when your app shows an error state after biometric failure, test verifies correct message renders regardless of which OS version renders underlying system dialog.

Concrete before-and-after on "biometric failure fallback to password" test flow.

Before, on Appium: Test triggers biometric prompt (works on simulator only, using Appium's mobile: sendBiometricMatch command). Simulates a failed match. Expects to find a fallback button by accessibility ID. That ID differs between iOS 15 and iOS 17. QA lead maintains two selectors and a version check. Meanwhile underlying UI has changed twice this year for accessibility reasons. Test is red on most CI runs. Nobody trusts what it's actually catching.

After, on Drizz: Same test written in plain English. Vision AI sees failure message on screen ("Face ID not recognized. Try again or use password") and taps "Use password" regardless of underlying accessibility ID. When OS updates change wording slightly to "Face ID didn't match. Enter password," test can be updated with new visible text in one line. No accessibility ID chase. No per-OS-version selector maintenance.

For biometric flows specifically, this matters because surrounding UI (before prompt, after prompt, error messages, fallback screens) is where you spend most of your testing energy. The biometric prompt itself is a small piece of flow. The recovery paths around it are where user experience lives or dies.

FAQ

What is biometric authentication testing in mobile apps?

Verifying that an app correctly handles biometric prompts, successes, failures, fallbacks, key invalidations, and lockouts across iOS and Android.

Can biometric authentication be tested on emulators?

Partially. Emulators handle happy path and errors but not hardware attestation or true key invalidation properly.

What are most common biometric bugs in production?

Poor handling of key invalidation after enrollment change, missing fallback UI on lockout, and silent failures on disable.

Do you need real devices for biometric testing?

Yes for security-critical flows. Emulators cover happy paths. Real devices needed for cryptographic keys and enrollment changes.

What's difference between BIOMETRIC_STRONG and BIOMETRIC_WEAK on Android?

BIOMETRIC_STRONG (Class 3) is hardware-backed and can unlock cryptographic keys. BIOMETRIC_WEAK (Class 2) is lower assurance and cannot.

Can biometric authentication be fully automated in tests?

Not fully. The biometric prompt is a system dialog. Everything around it can be automated. The prompt needs manual verification.

‍

About the Author:

Partha Sarathi Mohanty
Co-founder & CPO, Drizz
ISB-trained product leader with battle scars from Mensa, Zolo, BlackBuck, and Shadowfax, now turning AI-native testing into an actual roadmap.
Schedule a demo