1

Paging 3, with Room. I have created an app just like the sample here and start writing test for it.

Here is what I have in DAO :

@Query("SELECT * FROM Model")
fun getModels(): PagingSource<Int, Model>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(list: MutableList<Model>) : Completable

and I wanted to test it like this:

@OptIn(ExperimentalCoroutinesApi::class) 
class DAOTest {

    private lateinit var dao: Dao
    private lateinit var db: ModelDatabase
    private lateinit var viewModel: MyViewModel
    lateinit var context: Context
    private val testDispatcher = TestCoroutineDispatcher()

    @Before
    fun createDb() {

        Dispatchers.setMain(testDispatcher)
        context = InstrumentationRegistry.getInstrumentation().targetContext
        db = Room.inMemoryDatabaseBuilder(context, MyDatabase::class.java)
            .allowMainThreadQueries()
            .build()
        dao = db.Dao()
        viewModel =
            MyViewModel(MyRepository(db))
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        db.close()
    }

    @Test
    fun dataBase_insertAndGet_success() = runBlockingTest(testDispatcher) {
        val differ = AsyncPagingDataDiffer(
            MyAdapter.diffCallback,
            noopListUpdateCallback,
            testDispatcher,
            testDispatcher
        )
        dao.insertAll(
            mutableListOf(listOfModels)
        ).test().assertResult()

        val job = launch {
            viewModel.getList().collectLatest {
                differ.submitData(it)
            }
        }
        advanceUntilIdle()
        Truth.assertThat(differ.snapshot()).containsExactly(
            model1, model2,model3,model4)
        job.cancel()
    }

    private val noopListUpdateCallback = object : ListUpdateCallback {
        override fun onInserted(position: Int, count: Int) {}
        override fun onRemoved(position: Int, count: Int) {}
        override fun onMoved(fromPosition: Int, toPosition: Int) {}
        override fun onChanged(position: Int, count: Int, payload: Any?) {}
    }
}

Exactly like the test in the sample. Strange think is that when I run the test several times some is passing and some not(saying the differ.snapshot() is empty) This is happening too when I have several other tests in this file (testing updating and removing) and try to run the whole tests together, some passing and some not which the passing tests are different in each round.

and this is what I have in my ViewModel for getting the list:

fun getList(type: Int, search: String? = null): Flow<PagingData<Stock>> {

        return Pager(
            config = PagingConfig(
                pageSize = PAGE_SIZE,
                enablePlaceholders = true,
                maxSize = MAX_SIZE
            )
        ) {
           repository.getListFromDao()
        }.flow
            .cachedIn(viewModelScope)

everything is like the sample but don't know why this behavior is happening. I saw other posts like this which is exactly what I have done in my test, but still I have the same problem. I should mention that when I try to test a query like below without the returning type of PagingSource<Int, Model> :

@Query("SELECT * FROM Model")
fun getModels(): List<Model>

everything works fine. the test passes every time I run it. so I think there is something wrong with this part in the test:

val job = launch {
    viewModel.getList().collectLatest {
        differ.submitData(it)
    }
}

Will really happy if anyone could help and give some hints, since I am working on it for quite a long time. Thank you.

Bita Mirshafiee
  • 600
  • 8
  • 14
  • Does it help if you insert after launching the job for submitData? I'm not sure Paging will pick up invalidation in time if it races since you're not using the suspend version – dlam Jun 14 '21 at 03:15
  • Thank you for the comment. I did that and no not working. This is from android docs : Use the PagingSource class directly to use Kotlin coroutines for async loading. so it uses the suspend under the hood. and in the sample for lunching a job for differ.submitdata it says :submitData allows differ to receive data from PagingData, but suspends until invalidation, so we must launch this in a separate job. thought maybe it helps for figuring out the problems. have no idea what is happening under the hood. – Bita Mirshafiee Jun 14 '21 at 06:55
  • As you said it seems it is because of differ. as docs says: "Caution: The submitData() method suspends and does not return until either the PagingSource is invalidated or the adapter's refresh method is called. This means that code after the submitData() call might execute much later than you intend." and when I call differ.refresh() after launch when I run the test several times it is ok, but when I run all the test together, again some passes some not, but single run is ok. have no idea how to force differ to give me the result before assert. – Bita Mirshafiee Jun 14 '21 at 09:01

1 Answers1

0

I believe you need to wait for invalidation to happen, which is triggered via InvalidationTracker from Room's end on insert.

Room uses the ArchTaskExecutors by default, which you can override via RoomDatabase Builder or make synchronous with the helpful InstantTaskExecutorRule

@get:Rule
val instantRule = InstantTaskExecutorRule()
dlam
  • 3,547
  • 17
  • 20