Mockito:无法验证是否调用了挂起函数,因为 Continuation<T> 函数参数在后台不匹配

mhd*_*.95 6 android unit-testing mockito kotlin-coroutines

我正在为我定义的LocalDataSource类编写一些单元测试,这些类包装了 Room 数据库的功能DAO,我的代码如下所示:

房间DAO接口

@Dao
interface PersonDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(person: Person)

}
Run Code Online (Sandbox Code Playgroud)

本地数据源类

class PersonLocalDataSourceImpl(private val personDao: PersonDao) {

    suspend fun insert(dispatcher: CoroutineDispatcher, person: Person) =
        withContext(dispatcher) {
            personDao.insert(person)     // line 20
        }

}
Run Code Online (Sandbox Code Playgroud)

单元测试类

@ExperimentalCoroutinesApi
@RunWith(JUnit4::class)
class PersonLocalDataSourceTest : BaseLocalDataSourceTest() {
    
    @Test
    fun givenPersonLocalDataSource_WhenInsertPerson_ThenPersonDaoInsertFunctionCalledOnce() =
        runBlockingTest {

            withContext(testCoroutineDispatcher) {

                val personDao = Mockito.mock(PersonDao::class.java)
                val personLocalDataSource = PersonLocalDataSourceImpl(personDao)
                val person = mockPerson()


                personLocalDataSource.insert(testCoroutineDispatcher, person)

                Mockito.verify(personDao).insert(person)   // line 36

            }
        }

}
Run Code Online (Sandbox Code Playgroud)

我在运行测试时遇到此错误:

Argument(s) are different! Wanted:
personDao.insert( Person( id = ...) ),
Continuation at (my package).PersonLocalDataSourceTest$givenPersonLocalDataSource_WhenInsertPerson_ThenPersonDaoInsertFunctionCalledOnce$1$1.invokeSuspend(PersonLocalDataSourceTest.kt:37)

Actual invocation has different arguments:
personDao.insert(Person( id = ...),
Continuation at (my package).PersonLocalDataSourceImpl$insert$2.invokeSuspend(PersonLocalDataSourceImpl.kt:20)
Run Code Online (Sandbox Code Playgroud)

PS当我更改函数的定义时,测试通过,PersonLocalDataSourceImpl::insert如下所示:

override suspend fun insert(dispatcher: CoroutineDispatcher, person: Person) =
            personDao.insert(person)
Run Code Online (Sandbox Code Playgroud)

Chr*_*anB 5

翻译:博士

您可以使用coEverycoVerify来模拟结果并验证挂起函数。当您声明时,它们就会变得可用testImplementation "io.mockk:mockk:"

在下面的示例中,我展示了如何测试挂起函数。

协程规则

我正在使用这个自定义规则进行测试。

class CoroutineRule(
  val testCoroutineDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher(),
    TestCoroutineScope by TestCoroutineScope(testCoroutineDispatcher) {

    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(testCoroutineDispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()
        testCoroutineDispatcher.cleanupTestCoroutines()
    }

    /**
     * Convenience method for calling [runBlockingTest] on a provided [TestCoroutineDispatcher].
     */
    fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) {
        testCoroutineDispatcher.runBlockingTest(block)
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们定义一个简单的Repository接口Dao

class Repository(
  val dao: Dao,
  private val dispatcher: Dispatcher = Dispatchers.IO) {

  suspend fun load(): String = withContext(dispatcher) { dao.load() }
}

interface Dao() {
  suspend fun load(): String 

  fun fetch(): Flow<String>
}
Run Code Online (Sandbox Code Playgroud)

测试协程

模拟协程,您需要添加此依赖项:

testImplementation "io.mockk:mockk:"
Run Code Online (Sandbox Code Playgroud)

然后您可以使用coEverycoVerifycoMatchcoAssert、或coRun来模拟挂起函数。coAnswerscoInvoke

testImplementation "io.mockk:mockk:"
Run Code Online (Sandbox Code Playgroud)

这样您就不需要withContext在测试代码中进行任何上下文切换。您只需调用coroutineRule.runBlocking { ... }并设置您对模拟的期望即可。然后您可以简单地验证结果。

笔记

我认为你不应该从外面经过调度员。通过协程(和结构化并发),实现者(库、函数等)最清楚要在哪个调度程序上运行。当您有一个从数据库读取的函数时,该函数可以使用特定的调度程序Dispatchers.IO(如您在我的示例中看到的)。

通过结构化并发,调用者可以将结果调度到任何其他调度程序上。但它不应该负责决定下游功能应该使用哪些调度程序。

  • 我认为它们是在这里定义的:https://mockk.io/ - 我还没有在 kotlinx-coroutines-core 库中看到这些函数。 (2认同)