首页 > 解决方案 > 单元测试室 android - 这项工作尚未完成

问题描述

我目前正在对使用 Room 的本地数据源进行单元测试。我创建了一个测试类:

/**
 * Integration test for the [WatchListLocalDataSource].
 */
@RunWith(AndroidJUnit4::class)
@MediumTest
class WatchListLocalDataSourceTest {

    private lateinit var sut: WatchListLocalDataSourceImpl
    private lateinit var database: ShowsDatabase
    private lateinit var entityMapper: ShowEntityMapper

    private lateinit var testDispatcher: TestCoroutineDispatcher
    private lateinit var testScope: TestCoroutineScope

    @Before
    fun setup() {
        entityMapper = ShowEntityMapper()
        testDispatcher = TestCoroutineDispatcher()
        testScope = TestCoroutineScope(testDispatcher)
        val context = InstrumentationRegistry.getInstrumentation().context
        // using an in-memory database for testing, since it doesn't survive killing the process
        database = Room.inMemoryDatabaseBuilder(
            context,
            ShowsDatabase::class.java
        )
            .setTransactionExecutor(testDispatcher.asExecutor())
            .setQueryExecutor(testDispatcher.asExecutor())
            .build()

        sut = WatchListLocalDataSourceImpl(database.watchListDao(), entityMapper)
    }

    @After
    @Throws(IOException::class)
    fun cleanUp() {
        database.close()
    }

    @Test
    @Throws(Exception::class)
    fun observeWatchedShows_returnFlowOfDomainModel()  = testScope.runBlockingTest {

        val showId = 1
        sut.addToWatchList(mockShow(showId))

        val watchedShows: List<Show> = sut.observeWatchedShows().first()

        assertThat("Watched shows should contain one element", watchedShows.size == 1)
        assertThat("Watched shows element should be ${mockShow(showId).name}", watchedShows.first() == mockShow(showId))
    }
}

但是,测试没有完成,请注意:

java.lang.IllegalStateException: This job has not completed yet

中的实际方法sut是:

override suspend fun addToWatchList(show: Show) = withContext(Dispachers.IO) {
    watchListDao.insertShow(WatchedShow(entityMapper.mapFromDomainModel(show)))
}

标签: androidandroid-roomkotlin-coroutinesandroid-unit-testing

解决方案


所以问题从addToWatchList数据源中的方法开始,我明确地将它与 Dipachers.IO 协程范围不同,这是不必要的,因为如果您suspend为函数使用关键字,Room 会在内部处理线程。

这产生了一个问题,在测试协程范围上开始的工作正在生成一个新范围,并且由于房间需要在启动它的同一线程上完成,因此出现了死锁,从而产生了java.lang.IllegalStateException: This job has not completed yet错误。

解决方案是:

  1. withContext在 DAO 插入方法中删除,让 Room 自己处理范围。
  2. 在测试类的 @Before 方法中添加.allowMainThreadQueries()到数据库构建器,这允许空间使用提供的测试范围并确保所有工作都在该定义的范围内进行。

正确的代码是:

@Before
fun setup() {
    entityMapper = ShowEntityMapper()
    testDispatcher = TestCoroutineDispatcher()
    testScope = TestCoroutineScope(testDispatcher)
    val context = InstrumentationRegistry.getInstrumentation().context
    // using an in-memory database for testing, since it doesn't survive killing the process
    database = Room.inMemoryDatabaseBuilder(
        context,
        ShowsDatabase::class.java
    )
        .setTransactionExecutor(testDispatcher.asExecutor())
        .setQueryExecutor(testDispatcher.asExecutor())

        // Added this to the builder
                   |
                   v

        .allowMainThreadQueries()

        .build()

    sut = WatchListLocalDataSourceImpl(database.watchListDao(), entityMapper)
}

在 dataSource 类中:

override suspend fun addToWatchList(show: Show)  {
    watchListDao.insertShow(WatchedShow(entityMapper.mapFromDomainModel(show)))
}

推荐阅读