单元测试室 android - 此作业尚未完成

Unit testing Room android - This job has not completed yet

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

/**
 * 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. .allowMainThreadQueries() 添加到测试的 @Before 方法中的数据库构建器 class,这允许在提供的测试范围内工作并确保所有工作都在该定义的范围内进行.

正确的代码是:

@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)
}

并且在数据源中 class:

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