单元测试室 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)))
}

解决方法

所以问题始于数据源中的 addToWatchList 方法,我明确将其与 Dipachers.IO 协程范围不同,这是不必要的,因为如果您使用 suspend函数的关键字。

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

解决方案是:

  1. 删除 DAO 插入方法中的 withContext,让 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)))
}