如何使用 Mockk 模拟 android room withTransaction 方法

How to mock android room withTransaction method with Mockk

我正在尝试对我的业务逻辑进行一些单元测试。 我有一个存储库,我将一些来自响应的数据保存到房间数据库 (2.1.0-rc01)。 在单个事务中使用不同的 dao 将数据保存到不同的表中。 代码被简化:

ItemRepository

suspend fun saveItems(response: Response) {
    val items = response.items.map { it.toLocalItem() }
    val subItems = response.items.flatMap { item ->
            item.subItems.map { it.toLocal(item.id) }
        }

    db.withTransaction {
        db.itemDao().deleteAll()
        db.itemDao().insertAll(items)
        db.subItemDao().insertAll(subItems)
    }
}

对于单元测试,我使用的是 Mockk 库。我怎样才能模拟房间 withTransaction 方法?。 withTransaction 声明为

suspend fun <R> RoomDatabase.withTransaction(block: suspend () -> R): R

我正在尝试编写测试

@MockK
private lateinit var database: AppDatabase
@MockK
private lateinit var itemDao: ItemDao
@MockK
private lateinit var subItemDao: SubItemDao


@Test
fun checkSaveItems() = runBlocking {
    repository = ItemRepository(database)
    coEvery { database.itemDao() } returns itemDao
    coEvery { database.subItemDao() } returns subItemDao

    //TODO: execute database.withTransaction(block: suspend () -> R)

    coEvery { itemDao.deleteAll() } just Runs
    coEvery { itemDao.insertAll(any()) } just Runs
    coEvery { subItemDao.insertAll(any()) } just Runs

    repository.saveItems(testResponse)

    coVerifySequence {
        itemDao.deleteAll()
        itemDao.insertAll(testItems)
        subItemDao.insertAll(testSubItems)
    }
}

您首先必须为 Android Room KTX 方法 withTransaction {} 启用静态模拟。您还需要捕获传递给它的暂停 lambda 函数。这个捕获的函数可以被调用,所以它里面的代码 运行s。由于您正在模拟所有数据库调用,因此此处不需要真正的事务。

@Before
fun initMocks() {
  MockKAnnotations.init(this)

  mockkStatic(
            "androidx.room.RoomDatabaseKt"
  )

  val transactionLambda = slot<suspend () -> R>()
    coEvery { db.withTransaction(capture(transactionLambda)) } coAnswers {
      transactionLambda.captured.invoke()
    }
}

然后您应该能够运行编写您的代码。

为了扩展 Andrew 的回答,mockk documentation for extension functions 表明如果您正在模拟对象范围或 class 范围扩展函数,您可以只使用常规 mockk 来实现。但是,如果您使用 模块范围 扩展函数,例如 withTransaction,您还需要对模块的 class 名称执行 mockkStatic