How to Test Filters And Sorting on Android (Complete Guide)

Filters and sorting are the UI mechanisms that let users turn a large data set into a manageable view. In e‑commerce apps a filter might narrow products by price, brand, or availability; in a news rea

June 19, 2026 · 16 min read · How-To Guides

Motivation: Why Filters and Sorting Deserve Dedicated Test Effort

Filters and sorting are the UI mechanisms that let users turn a large data set into a manageable view. In e‑commerce apps a filter might narrow products by price, brand, or availability; in a news reader sorting could arrange articles by recency, popularity, or relevance. When these controls misbehave the user experience degrades instantly—items disappear, wrong order appears, or the app crashes while trying to apply a predicate. Because the logic often lives in the presentation layer, it is easy to overlook during unit testing, yet the failure surface is wide: UI state, backend queries, caching, and accessibility services all interact. A dedicated test effort catches regressions that would otherwise slip into production and generate support tickets, poor ratings, or abandoned carts.

What Typically Breaks in Production

Failure CategoryTypical SymptomRoot Cause
UI state mismatchFilter chip stays selected after clearingViewModel not observing LiveData correctly
Incorrect query generationSorting by price shows highest‑first despite ASC flagBackend receives wrong query parameter
Data stale after filterNew items appear only after pull‑to‑refreshCache invalidation missed
Accessibility lossTalkBack skips filter dropdownMissing contentDescription or focus order
Security leakFilter query logs user‑input to analyticsPII inadvertently sent
Performance dropUI freezes for >2 s when applying many‑choice filterHeavy work on main thread
Locale‑specific bugArabic layout shows filter icons mirrored incorrectlyRTL not handled in drawable resources
Race conditionRapid tap on sort button yields duplicate network callsNo debouncing or mutex

Understanding these patterns helps you build a test matrix that covers not only the happy path but also the failure modes that surface only under real‑world conditions.

Comprehensive Test Matrix

Below is a matrix you can copy into a test plan spreadsheet. Each row represents a test scenario; columns indicate the dimension you validate. Use ✅ for pass, ❌ for fail, and ⚠️ for flaky or conditional outcomes.

#ScenarioHappy PathError PathEdge CaseAccessibilitySecurity/PrivacyPerformanceLocalizationCross‑session StatePersona‑Driven
1Apply single‑choice filter (e.g., Category = Electronics)
2Clear all filters via “Reset” button
3Apply multiple‑choice filter (brand + price range)⚠️ (debounce)
4Select incompatible filters (e.g., Free + Paid)✅ (shows empty state)
5Apply filter when backend returns 500✅ (error toast)
6Sort ascending/descending on a numeric column
7Sort on a column with null values✅ (nulls at bottom/top per spec)
8Rapidly toggle sort order 10× in 2 s✅ (no duplicate requests)⚠️ (throttle)
9Apply filter while device rotates✅ (state retained)
10Filter with long‑text query (>100 chars)✅ (truncation or validation)✅ (no PII leak)
11Filter using voice input (Accessibility Service)✅ (TalkBack)
12Apply filter with TalkBack enabled, navigate via swipe✅ (focus moves)
13Filter when app is in background (data sync)✅ (UI reflects latest)
14Apply filter after clearing app cache/reinstall✅ (defaults restored)
15Filter with remote‑config toggle (feature flag)✅ (flag off hides UI)
16Sorting with custom comparator (locale‑aware)✅ (correct collation)✅ (Arabic/Thai)✔️✔️
17Power‑user: apply filter, then sort, then undo via back button✅ (state stack)✔️✔️
18Elderly persona: large‑font mode, filter touch target ≥48 dp✅ (touch target)✔️✔️
19Impatient persona: tap filter, then immediately exit screen✅ (no crash)✔️✔️
20Adversarial persona: inject SQL‑like string in filter text field✅ (sanitized, no error)✅ (no injection)✔️✔️

Feel free to add rows for specific product domains (e.g., date range filter for events, rating filter for reviews). The matrix gives you a concrete checklist that can be turned into automated test cases or manual exploratory sessions.

Manual Testing Approach – Step‑by‑Step

1. Prepare the Test Environment

2. Baseline Exploration

Launch the app and navigate to the screen containing the filter/sort UI.

3. Happy‑Path Validation

  1. Tap a single filter chip → verify it becomes highlighted.
  2. Observe the list update: item count changes, no stale items remain.
  3. Tap “Apply” (if present) → confirm network request matches expected query (use adb logcat or a proxy like Charles).
  4. Change sort order → confirm list re‑renders with correct comparator.

4. Error‑Path Injection

5. Edge‑Case Execution

6. Accessibility Checks

7. Security/Privacy Scan

8. Performance Profiling

9. Localization & RTL

10. Cross‑Session State

11. Persona‑Driven Exploration

Document any deviation from the matrix as a bug, attaching logs, screenshots, and steps to reproduce.

Automated Approaches – Espresso, UIAutomator, and Appium

Espresso for Filter UI


@RunWith(AndroidJUnit4::class)
class FilterSortTest {

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

    @Test
    fun `single choice filter updates list`() {
        // Arrange – ensure we start from known state
        onView(withId(R.id.btn_reset_filters)).perform(click())

        // Act – tap the Electronics chip
        onView(withText("Electronics")).perform(click())

        // Assert – chip appears selected
        onView(withText("Electronics"))
            .check(matches(isSelected()))

        // Assert – list reflects filter (e.g., at least one item shown)
        onView(withId(R.id.recycler_view))
            .check(matches(hasMinimumChildCount(1)))
    }

    @Test
    fun `sort descending shows highest price first`() {
        onView(withId(R.id.spinner_sort)).perform(click())
        onView(withText("Price: High → Low")).perform(click())

        onView(withId(R.id.recycler_view))
            .check(matches(
                atPosition(
                    0,
                    hasDescendant(withText(matches(`startsWith`("$"))))
                )
            ))
    }
}

*Key points*:

UIAutomator for Cross‑App Scenarios

If your filter launches a separate picker activity (e.g., date range), UIAutomator can interact across process boundaries:


public class FilterUiAutomatorTest {

    private UiDevice device;

    @Before
    public void setUp() throws Exception {
        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    }

    @Test
    public void dateRangeFilterAppliesCorrectly() throws UiObjectNotFoundException {
        // Open filter drawer
        device.findObject(new UiSelector().descriptionContains("Open filters")).click();

        // Set start date
        UiObject startDate = device.findObject(new UiSelector()
                .resourceId("com.example.app:id/start_date"));
        startDate.click();
        device.findObject(new UiSelector().text("15")).click(); // day 15

        // Set end date
        UiObject endDate = device.findObject(new UiSelector()
                .resourceId("com.example.app:id/end_date"));
        endDate.click();
        device.findObject(new UiSelector().text("22")).click(); // day 22

        // Confirm
        device.findObject(new UiSelector().text("Apply")).click();

        // Verify that at least one item matches the range
        UiObject list = device.findObject(new UiSelector()
                .resourceId("com.example.app:id/item_list"));
        assertTrue(list.getChildCount() > 0);
    }
}

Appium for Hybrid or WebViews

When part of the filter UI lives in a WebView (common for hybrid apps), Appium lets you switch contexts:


from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

caps = {
    "platformName": "Android",
    "deviceName": "Pixel_4_API_33",
    "app": "/path/to/app.apk",
    "automationName": "UiAutomator2"
}
driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)

# Switch to native context to open filter
driver.find_element(MobileBy.ACCESSIBILITY_ID, "Open filters").click()
# Switch to WebView
WebDriverWait(driver, 10).until(lambda d: len(d.contexts) > 1)
driver.context = driver.contexts[1]   # usually "WEBVIEW_com.example.app"

# Interact with HTML filter
driver.find_element(MobileBy.ID, "category-select").send_keys("Electronics")
driver.find_element(MobileBy.ID, "apply-btn").click()

# Switch back and validate list
driver.context = driver.contexts[0]
assert driver.find_element(MobileBy.ID, "item_count").text != "0"
driver.quit()

Leveraging SUSA for Autonomous, Persona‑Driven Discovery

SUSA can explore the filter/sort screen without any test code. After installing the agent:


pip install susatest-agent
susatest explore --apk path/to/app.apk --personas curious impatient elderly adversarial

The agent will: