How to Test Account Deletion on Android (Complete Guide)

Account deletion is a high‑risk operation because it touches user data, persisted state, backend services, and often triggers cascading clean‑up actions. A bug in this flow can lead to data leakage, o

January 22, 2026 · 18 min read · How-To Guides

Why Account Deletion Testing Matters

Account deletion is a high‑risk operation because it touches user data, persisted state, backend services, and often triggers cascading clean‑up actions. A bug in this flow can lead to data leakage, orphaned resources, or a broken user experience that triggers churn and regulatory penalties.

The business impact

When a user initiates delete, expectations are clear: the account and all associated personal data disappear from the service. If the app only hides the UI but leaves data on the server, users lose trust and may file complaints under GDPR, CCPA, or similar statutes. Conversely, an overly aggressive delete that removes shared content (e.g., group chat history) can anger other users and increase support load.

Technical risks

Deletion typically involves several moving parts:

  1. Local cache clearing – SharedPreferences, SQLite rooms, Files in internal/external storage.
  2. Network calls – One or more REST/GraphQL endpoints that may require fresh auth tokens, handle throttling, or retry on failure.
  3. Server‑side clean‑up – Cascading deletes in relational databases, removal from search indexes, revocation of refresh tokens, and GDPR‑style data‑subject requests.
  4. Concurrency – Background sync workers, push receivers, or JobScheduler tasks that may still be running when the delete request is sent.
  5. Fallback UI – After a successful delete the app should navigate to a logged‑out state or a welcome screen; a misrouted navigation leaves the user staring at a stale screen.

Any failure in these steps can manifest as a crash, an ANR, a silent data leak, or a zombie account that continues to consume resources.

Regulatory considerations

Privacy laws require that a deletion request be honoured within a defined time frame (often 30 days) and that the controller can demonstrate erasure. Testers must verify that the app not only sends the request but also validates the server response, handles error codes (e.g., 429 Too Many Requests, 500 Internal Server Error), and provides clear feedback to the user.

---

Test Matrix for Account Deletion

Below is a comprehensive matrix that covers the dimensions you should exercise. Each row represents a test case; columns indicate the observable outcome, the expected result, and notes on automation feasibility.

IDCategoryDescriptionPreconditionsStepsExpected ResultPass/Fail CriteriaAutomation Notes
A1Happy pathUser deletes account via settingsLogged in, network reachable, no pending syncOpen Settings → Account → Delete Account → Confirm → Wait for success toastAccount removed locally, server returns 200, app navigates to login/welcome screenPASS if toast shown, navigation correct, no local data remainsEspresso/UI Automator can assert toast and navigation
A2Happy path with biometricSame as A1 but requires fingerprint/Face ID to confirmBiometric enrolled, policy requires biometric for deleteSame as A1, system prompts biometric, user authenticatesDelete proceeds after biometric successPASS if biometric prompt appears and delete completesUse UiDevice.fingerprintAuthenticate() in UI Automator
B1Error – network lossDelete initiated, then airplane mode toggledLogged in, Wi‑Fi onStart delete, enable airplane mode before network call completesApp shows error toast, retains account, offers retryPASS if error shown, no data cleared, retry button functionalMock network with adb shell cmd connectivity airplane-mode on
B2Error – server 401 (token expired)Auth token stale at moment of deleteLogged in, token expired, refresh token unavailableAttempt deleteApp shows session expired, redirects to loginPASS if user sent to login, no delete request sentIntercept with MockWebServer, assert redirect
B3Error – server 429 (rate limit)Delete request throttledLogged in, server returns 429 with Retry-After headerAttempt deleteApp displays retry after X seconds, does not clear dataPASS if retry UI shown, no local wipeEnqueue 429 response in MockWebServer
C1Edge – race with syncBackground sync pushes new data while delete in flightSync interval < 5 s, delete triggers network callStart delete, force sync via adb shell cmd jobscheduler run …Delete either aborts with conflict error or waits for sync to finish then proceedsPASS if app handles conflict gracefully (shows message, does not lose data)Use JobScheduler API to trigger sync
C2Edge – multi‑accountUser has two accounts, deletes secondary while primary stays activeTwo accounts added, secondary selected in account pickerOpen account switcher, select secondary, initiate deleteSecondary removed, primary remains logged in, UI reflects correct accountPASS if only secondary data cleared, primary untouchedReset account state between runs with adb shell pm clear
C3Edge – low storageDevice storage < 10 MB when delete attempts to clear local DBFill storage via adb shell dd if=/dev/zero of=/data/local/tmp/junk bs=1M count=500Attempt deleteApp shows insufficient storage error, does not corrupt DBPASS if error shown, DB intact, no crashMonitor logcat for SQLiteFullException
D1Accessibility – TalkBackUser navigates delete flow with TalkBack enabledTalkBack on, focus order logicalSwipe to Settings → Account → Delete → ActivateDelete confirmation dialog announced, focus lands on Cancel then Confirm buttonsPASS if all controls labeled, focus order correct, announcement completeUse accessibilityService.performAction(GLOBAL_ACTION_BACK) in UI Automator test
D2Accessibility – color contrastDelete button must meet WCAG AA contrastDevice in high contrast mode or forced via developer optionsVerify delete button contrast ratio ≥ 4.5:1Button passes contrast checkPASS if contrast tool reports complianceUse Android’s View#getDrawingRect + pixel analysis in instrumentation test
E1Security – token leakageNetwork traffic inspected for auth tokens in delete requestEnable HTTP logging (e.g., adb shell setprop log.tag.HttpURLConnection VERBOSE)Perform deleteNo auth token appears in URL query params or logsPASS if token only in Authorization headerUse HttpLoggingInterceptor in automated test
E2Security – re‑authenticationDelete should require recent auth (e.g., within 5 min)Auth older than 5 min, no biometric fallbackAttempt deleteApp prompts for password/biometric before proceedingPASS if re‑auth screen appearsMock timestamp, assert challenge shown
E3Security – data remnants checkAfter delete, verify no personal data left in files or backupsUse run-as to inspect app’s data directoryDelete account, then run-as com.example.app ls -la /data/data/com.example.app/No files containing PII (emails, tokens, photos)PASS if grep for patterns returns nothingScript with adb shell run-as + grep -r
F1Flow – incomplete confirmationUser taps Delete but backs out before confirmationDelete button leads to confirmation dialogTap Delete, then press back or outside dialogDialog dismissed, account unchangedPASS if no toast, no network call, data unchangedAssert that no DELETE request sent (MockWebServer)
F2Flow – delayed successServer returns 202 Accepted, actual deletion asynchronousEndpoint returns 202 with job IDInitiate deleteApp shows “Deletion in progress” indicator, polls status, finishes with successPASS if indicator appears, polling stops, final state correctUse CountingIdlingResource for Espresso to wait on polling
G1Localization – RTL languagesDelete flow works in right‑to‑left locales (e.g., Arabic)Device locale set to ar‑EGOpen settings, navigate to deleteAll labels mirrored, buttons aligned correctlyPASS if layout mirrors, no clipped textUse UI Automator to assert layoutDirection = RIGHT_TO_LEFT
G2Localization – long stringsLanguage with lengthy delete confirmation (e.g., German)Locale set to de‑DEView confirmation dialogText fits, no truncation, buttons accessiblePASS if all text visible, no overlapScreenshot comparison or Espresso view assertions

How to read the matrix

---

Manual Testing Approach

Even with strong automation, a manual exploratory pass catches nuances that scripts may miss—especially around timing, system dialogs, and device‑specific behaviors. Below is a step‑by‑step guide you can follow on a physical device or an emulator.

Preparing the test environment

  1. Device state – Start with a clean user data partition: adb shell pm clear or use a factory image.
  2. Account setup – Create at least two test accounts (primary and secondary) via the app’s signup flow. Use disposable emails or a test backend that allows instant account creation.
  3. Network control – Install a traffic interceptor (e.g., NetHunter, Charles Proxy on Wi‑Fi, or adb reverse to a local MockWebServer) to enable status‑code injection and latency simulation.
  4. Accessibility tools – Enable TalkBack, Switch Access, and Font Scaling to maximum in Settings → Accessibility.
  5. Logging – Set adb logtag *:V or use pidcat to capture verbose logs; filter by your app’s tag for quick inspection.
  6. Battery & performance – Plug the device in, disable battery optimizations for the app, and optionally enable “Stay awake” while charging to avoid sleep‑related flakiness.

Step‑by‑step manual test flow

StepActionObservation points
1Launch app, log in with primary account.Verify home screen shows user‑specific data (e.g., profile name).
2Navigate to Settings → Account → Manage Account.Ensure the account entry is tappable, label readable.
3Tap Delete Account.Confirmation dialog should appear; check that TalkBack reads the message and button labels.
4(Optional) Change device locale to Arabic, repeat step 3.Layout should mirror; no clipping.
5Confirm deletion.Observe network traffic: a DELETE (or POST) request to /account/delete with proper auth header.
6Monitor UI feedback.Success toast, spinner, or progress bar should appear; error states should show appropriate messages.
7Wait for flow to finish.App should navigate to login screen or welcome screen; no remnants of previous account visible.
8Verify local data cleared.Run adb shell run-as ls -la /data/data// and inspect SharedPreferences, databases, and cache folders for any leftover files containing email, tokens, or user‑generated content.
9Check server side (if you have access).Confirm that the account record is marked deleted or removed; ensure no active sessions remain.
10Perform negative tests.Repeat steps 1‑9 while: airplane mode toggled mid‑request, token expired, low storage, TalkBack gestures, or a background sync job forced via adb shell cmd jobscheduler run ….
11Clean up.Log out, clear app data, and restore device settings (locale, accessibility, network proxy).

Observables and verification points

Common pitfalls

---

Automated Testing on Android

Automation gives repeatability and speed for the bulk of the matrix. Below are the layers you should implement, with concrete snippets that you can copy into a Gradle‑based Android project.

Unit and integration tests (JUnit 5 + Mockito)

Test the ViewModel or UseCase that orchestrates the delete flow. Mock the repository and network layer to simulate success, error, and edge responses.


// AccountDeleteViewModelTest.kt
@OptIn(ExperimentalCoroutinesApi::class)
class AccountDeleteViewModelTest {

    private lateinit var viewModel: AccountDeleteViewModel
    private lateinit var repository: MockAccountRepository
    private lateinit var testDispatcher: TestDispatcher

    @Before
    fun setUp() {
        testDispatcher = StandardTestDispatcher()
        Dispatchers.setMain(testDispatcher)
        repository = mock()
        viewModel = AccountDeleteViewModel(repository)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test
    fun `delete success updates state and navigates`() = runTest {
        // given
        whens(repository.deleteAccount())
            .thenReturn(Result.success(Unit))

        // when
        viewModel.deleteAccount()
        // trigger UI reaction
        viewModel.uiState.collectFirst { it } // assume UIState sealed class

        // then
        assertTrue(viewModel.uiState.value is UiState.Success)
        verify(navController).navigate(R.id.action_to_login)
    }

    @Test
    fun `delete network error shows error state`() = runTest {
        whens(repository.deleteAccount())
            .thenReturn(Result.error(IOException("timeout")))

        viewModel.deleteAccount()
        val state = viewModel.uiState.collectFirst { it }
        assertTrue(state is UiState.Error)
        assertEquals("Network timeout", (state as UiState.Error).message)
    }
}

Why this matters – Unit tests guard the business logic against regressions when you refactor the repository or change threading models. They run in a fraction of a second and can be part of every PR.

UI tests with Espresso

Espresso excels at verifying UI state, toast messages, and navigation. Use IdlingResource to wait for background network calls if you use OkHttp’s IdlingResource or a custom one for WorkManager.


// AccountDeleteEspressoTest.kt
@RunWith(AndroidJUnit4::class)
class AccountDeleteEspressoTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Before
    fun setUp() {
        // login helper – uses test credentials from res/values/test_strings.xml
        loginHelper.loginAs("testuser@example.com", "SecurePass123")
    }

    @Test
    fun deleteAccount_showsSuccessToast_andNavigatesToLogin() {
        // open settings
        onView(withId(R.id.action_settings)).perform(click())
        onView(withText(R.string.settings_account)).perform(click())
        onView(withId(R.id.btn_delete_account)).perform(click())

        // confirm dialog
        onView(withText(R.string.dialog_confirm_delete)).check(matches(isDisplayed()))
        onView(withId(R.id.button_confirm)).perform(click())

        // IdlingResource for network (assuming OkHttpIdlingResource)
        IdlingRegistry.getInstance().register(OkHttpIdlingResource.create("okhttp"))

        // success toast
        onView(withText(R.string.toast_account_deleted))
            .inRoot(isToast())
            .check(matches(isDisplayed()))

        // navigation to login
        intended(hasComponent(LoginActivity::class.java.name))
    }

    @After
    fun tearDown() {
        IdlingRegistry.getInstance().remove(OkHttpIdlingResource.create("okhttp"))
    }
}

Notes

UI tests with UI Automator

UI Automator is useful for cross‑app interactions (e.g., system dialogs, permission prompts) and for testing accessibility services.


// AccountDeleteUiAutomatorTest.java
@RunWith(AndroidJUnit4.class)
public class AccountDeleteUiAutomatorTest {

    private UiDevice device;

    @Before
    public void setUp() {
        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        // Ensure we start from a clean state
        device.executeShellCommand("pm clear com.example.app");
        launchApp();
        loginTestUser();
    }

    @Test
    public void testDeleteWithTalkBack() throws UiObjectNotFoundException {
        // Enable TalkBack via settings (requires ADB permission)
        device.executeShellCommand("settings put secure accessibility_enabled 1");
        device.executeShellCommand("settings put secure enabled_accessibility_services com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService");

        // Navigate to delete
        new UiObject(new UiSelector().description("Settings")).click();
        new UiObject(new UiSelector().text("Account")).click();
        new UiObject(new UiSelector().text("Delete Account")).click();

        // Confirmation dialog
        UiObject confirm = new UiObject(new UiSelector().text("Delete account?"));
        assertTrue(confirm.waitForExists(2000));
        new UiObject(new UiSelector().text("DELETE")).click();

        // Verify toast via accessibility event
        UiObject toast = new UiObject(new UiSelector().className("android.widget.Toast"));
        assertTrue(toast.waitForExists(5000));
        assertEquals("Account deleted", toast.getText());
    }

    private void launchApp() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.example.app", "com.example.app.MainActivity"));
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        getInstrumentation().startActivitySync(intent);
    }

    private void loginTestUser() {
        // fill email/password, click sign in – omitted for brevity
    }
}

When to use UI Automator

Using ADB commands for state checks

Sometimes the fastest way to assert a condition is a shell command. Integrate these into your test teardown or as helper methods.


# Verify no SharedPreferences entry for auth token
adb shell run-as com.example.app grep -r "auth_token" /data/data/com.example.app/shared_prefs/

# Check that the account table is empty
adb shell run-as com.example.app sqlite3 /data/data/com.example.app/databases/app.db \
    "SELECT COUNT(*) FROM accounts;"

# Ensure no leftover files in cache
adb shell run-as com.example.app find /data/cache/com.example.app -type f -name "*.tmp" | wc -l

You can wrap these in JUnit @After methods to fail the test if any unwanted residue is found.

Comparison of automation approaches

ApproachStrengthsWeaknessesTypical use‑case for deletion
Unit/JUnit + MockitoFast, deterministic, isolates logicNo UI or system interactionValidate ViewModel/repository logic, error handling, state transitions
EspressoReal UI thread, synchronized with app, good for toast/navigationLimited to single app, struggles with system dialogsVerify screen flows, button states, toast messages, navigation after delete
UI AutomatorCan interact with system UI, accessibility services, multiple appsSlower, more brittle, requires API 18+Test TalkBack, permission dialogs, multi‑account picker, system settings changes
ADB shell checksDirect access to filesystem, databases, logsNot integrated with test runner, requires manual parsingPost‑action validation of data cleanup, token removal, file remnants

A robust strategy combines all four: unit tests for logic, Espresso for happy‑path/UI, UI Automator for accessibility and system‑edge cases, and ADB assertions in test teardown to ensure no data remnants.

---

Autonomous, Persona‑Driven Exploration with SUSA

While scripted tests cover the matrix you anticipate, real users behave in unpredictable ways. An autonomous explorer‑‑SUSA (SUSATest) is an autonomous QA platform that, given an APK or a web URL, explores the app using a variety of user personas, each with a distinct behavior model (curious, impatient, novice, adversarial, elderly, accessibility‑focused, power‑user, etc.). It does not need pre‑written scripts; it discovers flows by interacting with UI elements, handling dialogs, scrolling, and typing based on the persona’s policy.

How SUSA works

  1. Ingestion – You upload the APK or point SUSA at a staging URL. The platform installs the app on a fleet of real or emulated devices.
  2. Persona engine – Each persona defines:
  1. Exploration loop – The agent selects a UI element according to the persona’s policy, performs the action, observes the result (new screen, toast, crash, ANR), and updates its internal graph of visited states.
  2. Learning – Dead ends (screens with no forward actions) are marked and avoided in later runs, making each session smarter.
  3. Reporting – After a run, SUSA outputs:

Persona profiles relevant to deletion

PersonaTypical behavior that surfaces deletion bugs
ImpatientRapidly taps the Delete button multiple times before the confirmation dialog appears, testing double‑tap handling and race conditions.
NoviceMay long‑press the Delete icon expecting a tooltip, or miss the confirmation and hit back, revealing missing undo or unclear affordances.
AdversarialEnters garbage text in any exposed fields (e.g., if the app asks for a reason before delete) or tries to invoke delete via accessibility service gestures to bypass UI guards.
ElderlyUses larger font sizes and slower gestures; can expose layout clipping or touch‑target size issues on the confirmation dialog.
AccessibilityRelies on TalkBack or Switch Access; ensures every element is labeled and reachable, catching missing content‑description on the delete icon or poorly announced dialogs.
Power userOpens the app, adds a second account, switches accounts, deletes the secondary while a background sync is active, probing multi‑account and concurrency bugs.

Because SUSA does not follow a predetermined script, it can stumble upon combinations that a manual tester might never think to try—like deleting an account while the device is in battery‑saver mode, or while a system update dialog is overlaying the settings screen.

What SUSA can uncover that scripts miss

Example output (truncated)


[Persona: Impatient] 
- Action: Tap Delete button (x3) within 400ms
- Observation: First tap shows confirmation dialog, second tap triggers a second DELETE request (server returned 429)
- Verdict: FAIL – Rate‑limit not handled client‑side; user sees spinner forever

[Persona: Accessibility] 
- Action: Enable TalkBack, navigate to Delete account via explore-by-touch
- Observation: Delete button lacks content‑description, announced as "unlabeled button"
- Verdict: FAIL – WCAG 2.4.7 (Focus Visible) & 4.1.2 (Name, Role, Value) violation

[Persona: Power user] 
- Action: Add second account, start sync, switch to secondary, delete while sync ongoing
- Observation: Delete request returns 500 Internal Server Error; local cache cleared, leaving orphaned files in /data/data/com.example.app/cache/
- Verdict: FAIL – Server error not retried, data leak risk

These findings would not appear in a scripted test that only follows the “Settings → Account → Delete” path with a single tap and a mocked 200 response.

Integrating SUSA into your CI

You can invoke the SUSA CLI as a step in your pipeline:


# .gitlab-ci.yml snippet
test_account_deletion:
  image: python:3.11
  script:
    - pip install susatest-agent
    - susatest-agent run \
        --apk ./app/build/outputs/apk/debug/app-debug.apk \
        --personas impatient,novice,adversarial,elderly,accessibility,poweruser \
        --flows account_deletion \
        --output junit.xml
  artifacts:
    reports:
      junit: junit.xml

The JUnit report can be parsed by your CI system to gate merges on any new failure.

---

Production‑Only Edge Cases

Even with exhaustive lab testing, certain conditions only manifest in the wild. Below are the most common production‑only pitfalls for account deletion, along with detection strategies.

Background sync conflicts

Many apps use WorkManager or SyncAdapter to periodically pull data (e.g., messages, feed items). If a sync job starts *after* the delete request has been sent but *before* the server acknowledges completion, the job may re‑insert data that the UI just cleared, leading to a “zombie” account.

Detection

Server‑side throttling and retry logic

Production services often enforce per‑user rate limits on destructive endpoints. A client that blindly retries on 5xx may hammer the endpoint and get blocked, while a client that gives up too early leaves the account in a half‑deleted state.

Detection

Token expiration during flow

If the delete request takes longer than the access token’s lifetime (e.g., due to poor network), the server may respond with 401. Apps that silently refresh the token and retry without re‑prompting the user can violate the expectation that deletion requires recent authentication.

Detection

Multi‑account scenarios

When a user has multiple accounts, deleting one should not affect the others. However, shared preferences or a global singleton (e.g., a Firebase instance) might inadvertently wipe tokens for all accounts.

Detection

Local data remnants in external storage

Some apps cache media (profile pictures, attachments) on external storage (/sdcard/Android/data//). Deleting the account may clear the internal DB but leave these files, creating a privacy leak.

Detection

Interruption by system dialogs

Incoming calls, low‑battery warnings, or system update prompts can overlay the app while the delete confirmation is visible. If the app dismisses the dialog incorrectly or leaks the confirmation state, the user may unintentionally confirm or cancel the delete.

Detection

---

Checklist for Account Deletion

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