【问题标题】:Test methods of Room DAO with Kotlin Coroutines and Flow使用 Kotlin Coroutines 和 Flow 测试 Room DAO 的方法
【发布时间】:2019-10-28 10:30:22
【问题描述】:

我正在尝试在我的 Room Dao 中从 LiveData 迁移到 Flow。应用程序运行良好,但我在测试行为方面遇到问题。当我运行测试时,它正在无限期地启动和运行。 我也尝试使用 kotlinx.coroutines.test runBlockingTest,但我遇到了“这项工作尚未完成”的问题,例如 here。有人可以指出正确的方向如何测试我的 CoresDao 的行为吗?

@Dao
interface CoresDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertCores(cores: List<Core>)

    @Transaction
    suspend fun replaceCoresData(cores: List<Core>) {
        deleteAllCores()
        insertCores(cores)
    }

    @Query("SELECT * FROM cores_table")
    fun getAllCores(): Flow<List<Core>>

    @Query("DELETE FROM cores_table")
    suspend fun deleteAllCores()
}

@RunWith(AndroidJUnit4::class)
class CoresDaoTest {

    private lateinit var database: SpaceDatabase
    private lateinit var coresDao: CoresDao

    private val testDispatcher = TestCoroutineDispatcher()

    private val testCoresList = listOf(core2, core3, core1)

    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)

        val context = InstrumentationRegistry.getInstrumentation().targetContext
        database = Room.inMemoryDatabaseBuilder(context, SpaceDatabase::class.java).build()
        coresDao = database.coresDao()
    }

    @After
    fun cleanup() {
        database.close()

        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test
    fun testGetAllCores(): Unit = runBlocking {
        withContext(Dispatchers.Main) {
            runBlocking { coresDao.insertCores(testCoresList) }

            val coresList = mutableListOf<Core>()
            coresDao.getAllCores().collect { cores -> coresList.addAll(cores) }

            assertThat(coresList.size, equalTo(testCoresList.size))
        }
    }
}

【问题讨论】:

    标签: android unit-testing android-room kotlin-coroutines kotlinx.coroutines.flow


    【解决方案1】:

    为了测试 Flow,我发现最好的 API 是 .take(n).toList()。您可以使用runBlockingTest,并且您不需要使用withContext 将执行移至另一个线程。

    您可以在此处找到其工作原理的示例: https://github.com/manuelvicnt/MathCoroutinesFlow/blob/master/app/src/test/java/com/manuelvicnt/coroutinesflow/fibonacci/impl/NeverEndingFibonacciProducerTest.kt#L38

    【讨论】:

    • 这可能是一个愚蠢的问题(我是 Flow n00b),但take(n) 是您想要从 FLOW 本身或从流中可能拥有的列表中获得多少项目?就像我的 DAO 方法返回一个 Flow>,所以如果我这样做 take(1) 会从 Flow 中检索我的列表吗?
    【解决方案2】:

    由于您已经在使用 TestCoroutineDispatcher,因此在您的示例中使用 runBlockingTest 不会做任何事情。 收集后您必须cancel Flow 或您在其中启动Flowscope

    编辑:可以找到此类规则的示例here

    【讨论】:

    • 链接不见了,但作者已经将规则提取到this库中测试Flows。
    【解决方案3】:

    原来我没有正确处理 Flow 收集和取消,这可能是问题的原因。下面是有效的代码。 更复杂的例子可以找到here

    @RunWith(AndroidJUnit4::class)
    class CoresDaoTest {
    
        private lateinit var database: SpaceDatabase
        private lateinit var coresDao: CoresDao
    
        private val testDispatcher = TestCoroutineDispatcher()
    
        private val testCoresList = listOf(core2, core3, core1)
    
        @get:Rule
        var instantTaskExecutorRule = InstantTaskExecutorRule()
    
        @Before
        fun setup() {
            Dispatchers.setMain(testDispatcher)
    
            val context = InstrumentationRegistry.getInstrumentation().targetContext
            database = Room
                .inMemoryDatabaseBuilder(context, SpaceDatabase::class.java)
                .setTransactionExecutor(Executors.newSingleThreadExecutor())
                .build()
            coresDao = database.coresDao()
        }
    
        @After
        fun cleanup() {
            database.close()
    
            Dispatchers.resetMain()
            testDispatcher.cleanupTestCoroutines()
        }
    
        @Test
        fun testInsertAndGetAllCores() = runBlocking {
            coresDao.insertCores(testCoresList)
    
            val latch = CountDownLatch(1)
            val job = launch(Dispatchers.IO) {
                coresDao.getAllCores().collect { cores ->
                    assertThat(cores.size, equalTo(testCoresList.size))
                    latch.countDown()
                }
            }
    
            latch.await()
            job.cancel()
        }
    

    【讨论】:

    • 虽然这可行,但如果 collect 中的代码抛出任何错误,例如断言失败,测试仍将无限期运行。
    • 感谢您指出这一点。目前我正在使用接受答案的方法,这似乎很好。
    • 我的测试和你的一样,一直在旋转。在collect { } 我的只是断言列表不为空,然后调用countDown()。然后,await()cancel() 调用。上面的方法与更复杂示例中的方法相同,无论如何都是复杂示例的功能之一。其中一个有一个produceIn() 电话。不知道有什么区别或我应该使用什么。我应该将我的应用程序转换为只返回 List 而不是这个 Flow 废话。
    猜你喜欢
    • 1970-01-01
    • 2020-09-13
    • 1970-01-01
    • 2020-05-29
    • 2020-05-13
    • 2019-07-23
    • 1970-01-01
    • 2018-07-19
    • 1970-01-01
    相关资源
    最近更新 更多