Implementing the Paging library in your app should be paired with a robust
testing strategy. You should test data loading components such as
[`PagingSource`](https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.md.txt) and
[`RemoteMediator`](https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.md.txt)
to ensure that they work as expected. You should also write end-to-end tests to
verify that all of the components in your Paging implementation work correctly
together without unexpected side effects.

This guide explains how to test the Paging library in the different
[architecture layers](https://developer.android.com/topic/architecture.md.txt) of your app as well as how to write
end-to-end tests for your entire Paging implementation.

## UI layer tests

Because Compose consumes Paging data declaratively through
`collectAsLazyPagingItems`, your UI layer tests can focus primarily on the
`Flow<PagingData<Value>>` emitted by your ViewModel. To write tests to verify
the data in the UI is as you expect, include the `paging-testing` dependency. It
contains the `asSnapshot` extension on a `Flow<PagingData<Value>>`. It offers
APIs in its lambda receiver that allow for mocking scrolling interactions. It
returns a standard `List<Value>` produced by the scrolling interactions mocked.
This lets you assert that the data being paged through contains the expected
elements generated by those interactions. This is illustrated in the following
snippet:

```kotlin
fun test_items_contain_one_to_ten() = runTest {
  // Get the Flow of PagingData from the ViewModel under test
  val items: Flow<PagingData<String>> = viewModel.items

  val itemsSnapshot: List<String> = items.asSnapshot {
    // Scroll to the 50th item in the list. This will also suspend till
    // the prefetch requirement is met if there's one.
    // It also suspends until all loading is complete.
    scrollTo(index = 50)
  }

  // With the asSnapshot complete, you can now verify that the snapshot
  // has the expected values
  assertEquals(
    expected = (0..50).map(Int::toString),
    actual = itemsSnapshot
  )
}
```

Alternatively, you can scroll until a given predicate is met as seen in the
snippet below:

```kotlin
fun test_footer_is_visible() = runTest {
  // Get the Flow of PagingData from the ViewModel under test
  val items: Flow<PagingData<String>> = viewModel.items

  val itemsSnapshot: List<String> = items.asSnapshot {
    // Scroll till the footer is visible
    appendScrollWhile {  item: String -> item != "Footer" }
  }
```

> [!NOTE]
> **Note:** The `asSnapshot()` lambda receiver offers more APIs such as `refresh()` and `flingTo` to test the effects of user interactions with the data returned as part of pagination.

### Testing transformations

You should also write unit tests that cover any transformations you apply to
the `PagingData` stream. Use the `asPagingSourceFactory`
extension. This extension is available on the following data types:

- `List<Value>`.
- `Flow<List<Value>>`.

The choice of which extension to use depends on what you're trying to test.
Use:

- `List<Value>.asPagingSourceFactory()`: If you want to test static transformations like `map()` and `insertSeparators()` on data.
- `Flow<List<Value>>.asPagingSourceFactory()`: If you want to test how updates to your data, like writing to the backing data source, affects your paging pipeline.

> [!NOTE]
> **Note:** Each emission of the `Flow` used with `asPagingSourceFactory` is a new generation of `PagingData`. This lets you test `PagingSource` [invalidation](https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.md.txt#invalidate()) behavior in your UI.

To use either of these extensions follow the following pattern:

- Create the `PagingSourceFactory` using the appropriate extension for your needs.
- Use the returned `PagingSourceFactory` in a [fake](https://developer.android.com/training/testing/fundamentals/test-doubles.md.txt) for your [`Repository`](https://developer.android.com/topic/architecture/data-layer.md.txt).
- Pass that `Repository` to your `ViewModel`.

The `ViewModel` can then be tested as covered in the previous section.
Consider the following `ViewModel`:

```kotlin
class MyViewModel(
  myRepository: myRepository
) {
  val items = Pager(
    config: PagingConfig,
    initialKey = null,
    pagingSourceFactory = { myRepository.pagingSource() }
  )
  .flow
  .map { pagingData ->
    pagingData.insertSeparators<String, String> { before, _ ->
      when {
        // Add a dashed String separator if the prior item is a multiple of 10
        before.last() == '0' -> "---"
        // Return null to avoid adding a separator between two items.
        else -> null
      }
  }
}
```

To test the transformation in `MyViewModel`, supply a fake instance of
`MyRepository` that delegates to a static `List` representing the data to be
transformed as shown in the following snippet:

```kotlin
class FakeMyRepository() : MyRepository {
    private val items = (0..100).map(Any::toString)
    private val pagingSourceFactory = items.asPagingSourceFactory()

    // Expose as a function so a new PagingSource instance is
    // created each time it is called by the Pager
    fun pagingSource() = pagingSourceFactory()
}
```

You can then write a test for the separator logic as in the following snippet:

```kotlin
fun test_separators_are_added_every_10_items() = runTest {
  // Create your ViewModel
  val viewModel = MyViewModel(
    myRepository = FakeMyRepository()
  )
  // Get the Flow of PagingData from the ViewModel with the separator transformations applied
  val items: Flow<PagingData<String>> = viewModel.items

  val snapshot: List<String> = items.asSnapshot()

  // With the asSnapshot complete, you can now verify that the snapshot
  // has the expected separators.
}
```

### Test Compose load states

To test how your Compose UI components react to different load states (such as
`LoadState.Loading` or `LoadState.Error`) in isolation, emit static
`PagingData` snapshots with explicit [`LoadStates`](https://developer.android.com/reference/kotlin/androidx/paging/LoadStates.md.txt) using
`PagingData.empty` and `PagingData.from` through a `MutableStateFlow`:

```kotlin
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun userListScreen_displaysLoadingDataAndErrorStates() {
  val idle = LoadState.NotLoading(endOfPaginationReached = false)
  // Emit static PagingData snapshots to test UI state transitions without a
  // Pager or ViewModel.
  val pagingDataFlow = MutableStateFlow(PagingData.empty<User>())

  composeTestRule.setContent {
    val lazyPagingItems = pagingDataFlow.collectAsLazyPagingItems()
    UserListScreen(lazyPagingItems = lazyPagingItems)
  }

  // Verify the initial loading UI before any items arrive.
  pagingDataFlow.value = PagingData.empty(
    sourceLoadStates = LoadStates(
      refresh = LoadState.Loading,
      prepend = idle,
      append = idle
    )
  )
  composeTestRule.onNodeWithTag("LoadingSpinner").assertIsDisplayed()

  // Verify that loaded items replace the loading indicator.
  pagingDataFlow.value = PagingData.from(
    data = listOf(User(id = "1", label = "User A")),
    sourceLoadStates = LoadStates(
      refresh = idle,
      prepend = idle,
      append = idle
    )
  )
  composeTestRule.onNodeWithTag("LoadingSpinner").assertIsNotDisplayed()
  composeTestRule.onNodeWithText("User A").assertIsDisplayed()

  // Verify that a refresh failure surfaces the error message in the UI.
  pagingDataFlow.value = PagingData.empty(
    sourceLoadStates = LoadStates(
      refresh = LoadState.Error(IOException("Network unavailable")),
      prepend = idle,
      append = idle
    )
  )
  composeTestRule.onNodeWithText("Network unavailable").assertIsDisplayed()
}
```

## Data layer tests

Write unit tests for the components in your data layer to ensure that they load
the data from your data sources appropriately. Provide [fake](https://developer.android.com/training/testing/fundamentals/test-doubles.md.txt) versions of
dependencies to verify that the components being tested function correctly in
isolation. The main components that you need to test in the repository layer are
the `PagingSource` and the `RemoteMediator`.

### `PagingSource` tests

Unit tests for your `PagingSource` implementation involve setting up the
`PagingSource` instance and loading data from it with a `TestPager`.

To set up the `PagingSource` instance for testing, provide fake data to the
constructor. This gives you control over the data in your tests. In the
following example, the `RedditApi` parameter is a [Retrofit](https://square.github.io/retrofit/) interface that
defines the server requests and the response classes. A fake version can
implement the interface, override any required functions, and provide
convenience methods to configure how the fake object should react in tests.

After the fakes are in place, set up the dependencies and initialize the
`PagingSource` object in the test. The following example demonstrates
initializing the `FakeRedditApi` object with a list of test posts, and testing
the `RedditPagingSource` instance:

```kotlin
class SubredditPagingSourceTest {
  private val mockPosts = listOf(
    postFactory.createRedditPost(DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(DEFAULT_SUBREDDIT)
  )
  private val fakeApi = FakeRedditApi().apply {
    mockPosts.forEach { post -> addPost(post) }
  }

  @Test
  fun loadReturnsPageWhenOnSuccessfulLoadOfItemKeyedData() = runTest {
    val pagingSource = RedditPagingSource(
      fakeApi,
      DEFAULT_SUBREDDIT
    )

    val pager = TestPager(CONFIG, pagingSource)

    val result = pager.refresh() as LoadResult.Page

    // Write assertions against the loaded data
    assertThat(result.data)
    .containsExactlyElementsIn(mockPosts)
    .inOrder()
  }
}
```

The `TestPager` also lets you do the following:

- Test consecutive loads from your `PagingSource`:

```kotlin
    @Test
    fun test_consecutive_loads() = runTest {

      val page = with(pager) {
        refresh()
        append()
        append()
      } as LoadResult.Page

      assertThat(page.data)
      .containsExactlyElementsIn(testPosts)
      .inOrder()
    }
```

- Test error scenarios in your `PagingSource`:

```kotlin
    @Test
    fun refresh_returnError() {
        val pagingSource = RedditPagingSource(
          fakeApi,
          DEFAULT_SUBREDDIT
        )
        // Configure your fake to return errors
        fakeApi.setReturnsError()
        val pager = TestPager(CONFIG, source)

        runTest {
            source.errorNextLoad = true
            val result = pager.refresh()
            assertTrue(result is LoadResult.Error)

            val page = pager.getLastLoadedPage()
            assertThat(page).isNull()
        }
    }
```

### `RemoteMediator` tests

The goal of the `RemoteMediator` unit tests is to verify that the `load()`
function returns the correct
[`MediatorResult`](https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult.md.txt).
Tests for side effects, such as data being inserted into the database, are
better suited for [integration tests](https://developer.android.com/topic/libraries/architecture/paging/test.md.txt#end-to-end).

The first step is to determine what dependencies your `RemoteMediator`
implementation needs. The following example demonstrates a `RemoteMediator`
implementation that requires a Room database, a Retrofit interface, and a search
string:

```kotlin
@OptIn(ExperimentalPagingApi::class)
class PageKeyedRemoteMediator(
  private val db: RedditDb,
  private val redditApi: RedditApi,
  private val subredditName: String
) : RemoteMediator&lt;Int, RedditPost&gt;() {
  ...
}
```

You can provide the Retrofit interface and the search string as demonstrated in
the [PagingSource tests](https://developer.android.com/topic/libraries/architecture/paging/test.md.txt#pagingsource-tests) section. Providing a mock version
of the Room database is very involved, so it can be easier to provide an
[in-memory implementation](https://developer.android.com/training/data-storage/room/testing-db.md.txt#android) of
the database instead of a full mock version. Because creating a Room database
requires a [`Context`](https://developer.android.com/reference/kotlin/android/content/Context.md.txt) object, you must
place this `RemoteMediator` test in the `androidTest` directory and execute it
with the AndroidJUnit4 test runner so that it has access to a test application
context. For more information about instrumented tests, see [Build instrumented
unit tests](https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests).

Define tear-down functions to ensure that state does not leak between test
functions. This ensures consistent results between test runs.

```kotlin
@ExperimentalPagingApi
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class PageKeyedRemoteMediatorTest {
  private val postFactory = PostFactory()
  private val mockPosts = listOf(
    postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT),
    postFactory.createRedditPost(SubRedditViewModel.DEFAULT_SUBREDDIT)
  )
  private val mockApi = mockRedditApi()

  private val mockDb = RedditDb.create(
    ApplicationProvider.getApplicationContext(),
    useInMemory = true
  )

  @After
  fun tearDown() {
    mockDb.clearAllTables()
    // Clear out failure message to default to the successful response.
    mockApi.failureMsg = null
    // Clear out posts after each test run.
    mockApi.clearPosts()
  }
}
```

The next step is to test the `load()` function. In this example, there are three
cases to test:

- The first case is when `mockApi` returns valid data. The `load()` function should return `MediatorResult.Success`, and the `endOfPaginationReached` property should be `false`.
- The second case is when `mockApi` returns a successful response, but the returned data is empty. The `load()` function should return `MediatorResult.Success`, and the `endOfPaginationReached` property should be `true`.
- The third case is when `mockApi` throws an exception when fetching the data. The `load()` function should return `MediatorResult.Error`.

Follow these steps to test the first case:

1. Set up the `mockApi` with the post data to return.
2. Initialize the `RemoteMediator` object.
3. Test the `load()` function.

```kotlin
@Test
fun refreshLoadReturnsSuccessResultWhenMoreDataIsPresent() = runTest {
  // Add mock results for the API to return.
  mockPosts.forEach { post -> mockApi.addPost(post) }
  val remoteMediator = PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  )
  val pagingState = PagingState&lt;Int, RedditPost&gt;(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue { result is MediatorResult.Success }
  assertFalse { (result as MediatorResult.Success).endOfPaginationReached }
}
```

The second test requires the `mockApi` to return an empty result. Because you
clear the data from the `mockApi` after each test run, it will return an empty
result by default.

```kotlin
@Test
fun refreshLoadSuccessAndEndOfPaginationWhenNoMoreData() = runTest {
  // To test endOfPaginationReached, don't set up the mockApi to return post
  // data here.
  val remoteMediator = PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  )
  val pagingState = PagingState&lt;Int, RedditPost&gt;(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue { result is MediatorResult.Success }
  assertTrue { (result as MediatorResult.Success).endOfPaginationReached }
}
```

The final test requires the `mockApi` to throw an exception so that the test can
verify that the `load()` function correctly returns `MediatorResult.Error`.

```kotlin
@Test
fun refreshLoadReturnsErrorResultWhenErrorOccurs() = runTest {
  // Set up failure message to throw exception from the mock API.
  mockApi.failureMsg = "Throw test failure"
  val remoteMediator = PageKeyedRemoteMediator(
    mockDb,
    mockApi,
    SubRedditViewModel.DEFAULT_SUBREDDIT
  )
  val pagingState = PagingState&lt;Int, RedditPost&gt;(
    listOf(),
    null,
    PagingConfig(10),
    10
  )
  val result = remoteMediator.load(LoadType.REFRESH, pagingState)
  assertTrue {result is MediatorResult.Error }
}
```

## End-to-end tests

Unit tests provide assurance that individual Paging components work in
isolation, but end-to-end tests provide more confidence that the application
works as a whole. These tests help with verifying that your data layer
(`PagingSource` or `RemoteMediator`), `ViewModel`, and Compose UI integrate
seamlessly without unexpected side effects. The tests will still need some mock
dependencies, but generally they will cover most of your app code.

The example in this section uses a mock API dependency to avoid using the
network in tests. The mock API is configured to return a consistent set of test
data, resulting in repeatable tests. For end-to-end tests, you typically swap
out your real network API for a fake one, but you still let the Paging library
handle the actual fetching and local database caching (if using a
`RemoteMediator`) to maintain the fidelity of your tests.

Write your code in a way that lets you easily swap in mock versions of your
dependencies. The following example uses a basic [service locator
implementation](https://developer.android.com/training/dependency-injection#di-alternatives) and sets up a test with a mock API to verify that a Compose
screen properly consumes and displays the paged data. In larger apps, using a
dependency injection library like [Hilt](https://developer.android.com/training/dependency-injection/hilt-android.md.txt) can help manage more-complex
dependency graphs.

After you set up the test structure, the next step is to verify that the data
returned by the `Pager` implementation is correct. One test should verify that
the Compose UI populates with the correct items when the screen first loads, and
another test should verify that the UI correctly loads additional data based on
user interaction.

In the following example, the test verifies that the UI displays the expected
paged data.

```kotlin
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertDoesNotExist
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RedditScreenTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    private val postFactory = PostFactory()
    private val mockApi = MockRedditApi()

    @Before
    fun setup() {
        // Pre-populate the mock API with test data for the default subreddit
        mockApi.addPost(postFactory.createRedditPost(subreddit = "androiddev", title = "Jetpack Compose Paging"))

        // Swap your real dependency injection module/Service Locator with the mock API
        ServiceLocator.swap(
            object : DefaultServiceLocator(useInMemoryDb = true) {
                override fun getRedditApi(): RedditApi = mockApi
            }
        )
    }

    @Test
    fun loadsTheDefaultResults() = runTest {
        // 1. Set the Compose UI content
        composeTestRule.setContent {
            MyTheme {
                // Assume that this composable uses `collectAsLazyPagingItems()` internally
                RedditScreen(initialSubreddit = "androiddev")
            }
        }

        // 2. Wait for the asynchronous Paging loads to complete
        composeTestRule.waitUntilExactlyOneExists(
            matcher = hasText("Jetpack Compose Paging"),
            timeoutMillis = 5000
        )

        // 3. Assert that the loaded paged items are displayed correctly on screen
        composeTestRule.onNodeWithText("Jetpack Compose Paging").assertIsDisplayed()
    }

    @Test
    fun loadsNewDataBasedOnUserInput() = runTest {
        // Add data for a different subreddit to the mock API
        mockApi.addPost(postFactory.createRedditPost(subreddit = "compose", title = "Compose Testing"))

        composeTestRule.setContent {
            MyTheme {
                RedditScreen(initialSubreddit = "androiddev")
            }
        }

        // Wait for the initial load to finish
        composeTestRule.waitUntilExactlyOneExists(hasText("Jetpack Compose Paging"))

        // Simulate user entering a new subreddit in a text field and clicking search
        composeTestRule.onNodeWithTag("SubredditInput").performTextClearance()
        composeTestRule.onNodeWithTag("SubredditInput").performTextInput("compose")
        composeTestRule.onNodeWithTag("SearchButton").performClick()

        // Wait for the new paged data to load
        composeTestRule.waitUntilExactlyOneExists(
            matcher = hasText("Compose Testing"),
            timeoutMillis = 5000
        )

        // Assert the old data is gone and the new data is displayed
        composeTestRule.onNodeWithText("Jetpack Compose Paging").assertDoesNotExist()
        composeTestRule.onNodeWithText("Compose Testing").assertIsDisplayed()
    }
}
```

Because `Flow<PagingData>` loads data asynchronously, you must give the Paging
library time to fetch the initial load and emit it to
`collectAsLazyPagingItems` before making assertions. To do this, use
`composeTestRule.waitUntil` or `waitUntilExactlyOneExists`, as shown in the
preceding example.

After the data is loaded, you can assert directly against the Compose semantic
tree using `onNodeWithText` to verify that the items are actually rendered in
your `LazyColumn`.

## Additional resources

### Views content

- [Test your Paging implementation (Views)](https://developer.android.com/topic/libraries/architecture/views/paging/test-views.md.txt)

## Recommended for you

- Note: link text is displayed when JavaScript is off
- [Page from network and database](https://developer.android.com/topic/libraries/architecture/paging/v3-network-db.md.txt)
- [Migrate to Paging 3](https://developer.android.com/topic/libraries/architecture/paging/v3-migration.md.txt)
- [Load and display paged data](https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data.md.txt)