Paging 3 testing and interview lab
Test layers
- PagingSource: keys, load sizes/types, end, error, refresh key, cancellation
- RemoteMediator: REFRESH/APPEND/PREPEND, scoped keys, atomic writes, errors, freshness initialize
- transformation: mapping/filter/separators/order
- UI: initial/append/refresh loading, cached failure, empty, retry
- end-to-end: fake server + real Room + Pager + UI
val snapshot = repository.products(query).asSnapshot {
scrollTo(index = 40)
}
assertEquals(expectedIds, snapshot.map(Product::id))
Use TestPager for PagingSource and asSnapshot from paging-testing for Flow. RemoteMediator with Room generally uses Android test/in-memory DB. Assert DB rows and remote keys, not just MediatorResult.Success.
Duplicate-request tests
Count requests and verify cursors under:
- two collectors/configuration recreation
- rapid scroll near boundary
- invalidation during append
- refresh while append active
- retry after failure
- repeated empty page
- query switch/cancellation
Ensure fake server respects realistic cursor/end semantics; a fake returning same page can make production code appear broken or hide dedupe flaws.
Interview scenarios
Why cachedIn?
It shares/caches PagingData events in a scope, allows reuse/recollection and avoids repeated upstream load/transform across configuration. It is not disk cache.
PagingSource versus RemoteMediator?
PagingSource reads one source into pages. RemoteMediator reacts to boundary/refresh needs, fetches remote data, and writes DB; a Room PagingSource then reads database truth.
Why store remote keys transactionally?
Data and continuation position must advance together. Otherwise crash can skip or duplicate pages.
Case study: offline product search
- query/account/sort scope both products and remote key
- Room ordered by score/time + unique ID
- Pager constructed per debounced query and cached in ViewModel
- RemoteMediator refreshes/appends into DB transaction
- pending favorites survive refresh deletion
- cached results remain visible on network failure
- UI distinguishes source rows from mediator update state
- tests kill/invalidate between response and transaction, switch queries, and retry cursor failures
Review checklist
- Key/end/refresh behavior follows backend contract.
- PagingSource factory creates a new source.
- Ordering is deterministic with unique tie-break.
- Remote keys include all query/account dimensions.
- Rows and keys commit atomically.
- Refresh preserves local-only/pending state.
- Transformations and cachedIn order are intentional.
- Load states distinguish usable content and source/mediator.
- Process recreation rebuilds from durable inputs/data.
- Tests assert requests, DB, keys, UI, and concurrency.
Explain the key protocol, generation/invalidation, local truth and mediator transaction, cache scope, load-state UX, process recovery, and deterministic tests.
Sources
Exercises
- Write a duplicate-request test matrix.
- Test cursor/row atomicity failure.
- Design load-state UI for cached data plus failed refresh.