【问题标题】:Is it possible to spy on suspend Android Room DAO functions with MockK是否可以使用 MockK 监视暂停 Android Room DAO 功能
【发布时间】:2020-08-01 11:27:50
【问题描述】:

我正在使用我的 Android JUnit 测试调查 MockK 库

testImplementation "io.mockk:mockk:1.10.0"

我在尝试监视挂起函数时遇到问题

这是我的 Junit 测试

@ExperimentalCoroutinesApi
@FlowPreview
@RunWith(AndroidJUnit4::class)
class BackOffCriteriaDaoTest : BaseTest() {

    @Rule
    @JvmField
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    private lateinit var dao: BackoffCriteriaDAO

        @Test
        fun backOffCriteria() = runBlocking {
            dao = spyk(myRoomDatabase.backoffCriteriaDAO())
            assertNotNull(dao.getBackoffCriteria())
            assertEquals(backOffCriteriaDO, dao.getBackoffCriteria())
            dao.delete()
    
            coVerify {
                myRoomDatabase.backoffCriteriaDAO()
                dao.reset()
            }
        }
    }

这个测试在dao.reset() 抛出一个java.lang.AssertionError 如下:-

java.lang.AssertionError: Verification failed: call 2 of 2: BackoffCriteriaDAO_Impl(#2).reset(eq(continuation {}))). Only one matching call to BackoffCriteriaDAO_Impl(#2)/reset(Continuation) happened, but arguments are not matching:
[0]: argument: continuation {}, matcher: eq(continuation {}), result: -

我的 dao reset() 方法类似于:-

@Transaction
suspend fun reset() {
    delete()
    insert(BackoffCriteriaDO(THE_BACKOFF_CRITERIA_ID, BACKOFF_CRITERIA_MILLISECOND_DELAY, BACKOFF_CRITERIA_MAX_RETRY_COUNT))
}

为什么我会看到这个java.lang.AssertionError? 我如何coVerify 已调用挂起函数?

更新

我相信这个问题是由于我使用的是 Room 数据库造成的。 我的dao接口方法reset()是通过房间生成代码实现的

 @Override
  public Object reset(final Continuation<? super Unit> p0) {
    return RoomDatabaseKt.withTransaction(__db, new Function1<Continuation<? super Unit>, Object>() {
      @Override
      public Object invoke(Continuation<? super Unit> __cont) {
        return BackoffCriteriaDAO.DefaultImpls.reset(BackoffCriteriaDAO_Impl.this, __cont);
      }
    }, p0);
  }

这意味着 coVerify{} 匹配的是这个函数,而不是我的接口版本。

是否可以匹配这个生成的public Object reset(final Continuation&lt;? super Unit&gt; p0) 版本?

这是一个更基本的 mockk 问题,它不能模拟 java 类吗? 还是 Kotlin 接口的 Java 实现?

更新 2

当我的 Room DAO 功能未暂停时,Mockk 将按要求工作

在我的 DAO 中使用这些虚拟函数:-

@Transaction
fun experimentation() {
    experiment()
}

@Transaction
fun experiment() {
    experimental()
}

@Query("DELETE from backoff_criteria")
fun experimental()

我的测试通过了

@Test
fun experimentation() = runBlocking {
    val actual = myRoomDatabase.backoffCriteriaDAO()
    val dao = spyk(actual)

    dao.experimentation()

    verify { dao.experiment() }
}

当我如下更改我的虚拟函数时,测试仍然通过

@Transaction
suspend fun experimentation() {
    experiment()
}

@Transaction
fun experiment() {
    experimental()
}

@Query("DELETE from backoff_criteria")
fun experimental()

但是,当我如下更改我的虚拟函数时,测试会引发异常

@Transaction
suspend fun experimentation() {
    experiment()
}

@Transaction
suspend fun experiment() {
    experimental()
}

@Query("DELETE from backoff_criteria")
fun experimental()

失败的测试类似于:-

@Test
fun experimentation() = runBlocking {
    val actual = myRoomDatabase.backoffCriteriaDAO()
    val dao = spyk(actual)

    dao.experimentation()

    coVerify { dao.experiment() }

}

例外是

java.lang.AssertionError: Verification failed: call 1 of 1: BackoffCriteriaDAO_Impl(#2).experiment(eq(continuation {}))). Only one matching call to BackoffCriteriaDAO_Impl(#2)/experiment(Continuation) happened, but arguments are not matching:
[0]: argument: continuation {}, matcher: eq(continuation {}), result: -

【问题讨论】:

  • 只是一个想法,您在验证之前致电dao.delete()。但是,您正在验证的方法 dao.reset() 在其体内也有 insert(...)。从错误来看,在我看来,insert(...) 从未被调用,因此您的 coVerify 失败。
  • 感谢您查看我的问题。我相信我的问题(来自 AssertionError)是因为我的 reset() 函数是一个挂起函数,所以添加了一个延续函数 arg,这意味着它与我放置在 coVerify{} 块中的 no Args 方法不匹配。是否可以使用 mockk/spyk 匹配挂起函数?

标签: android android-room junit4 mockk


【解决方案1】:

spy 可能没有问题,但您正在调用的事务函数的异步性质。

要使用具有范围的挂起函数进行测试,您可能需要使用

launch builder 并提前一段时间直到空闲,或者一段时间来测试进度,就像使用 RxJava 计数器部件一样。

我在使用 MockWebServer 时遇到了同样的问题,您可以在这里查看 question

  launch {
         dao.delete()
    }

    advanceUntilIdle()

并将协程规则与测试一起使用,以使每个操作具有相同的范围。

class TestCoroutineRule : TestRule {

    private val testCoroutineDispatcher = TestCoroutineDispatcher()

    val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)

    override fun apply(base: Statement, description: Description?) = object : Statement() {

        @Throws(Throwable::class)
        override fun evaluate() {

            Dispatchers.setMain(testCoroutineDispatcher)

            base.evaluate()

            Dispatchers.resetMain()
            try {
                testCoroutineScope.cleanupTestCoroutines()
            } catch (exception: Exception) {
                exception.printStackTrace()
            }
        }
    }

    fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
        testCoroutineScope.runBlockingTest { block() }

}

您可以使用如下规则

 testCoroutineRule.runBlockingTest {

           dao.delete()

           advanceUntilIdle()
           
            coVerify {
                myRoomDatabase.backoffCriteriaDAO()
                dao.reset()
            }

        }

您也可以尝试将 dao.delete() 放入 launch。在某些测试中,如果没有启动它就无法工作,而在其他一些测试中,如果没有它,甚至有些测试对我尝试的所有东西都很不稳定。 coroutines-test 有一些问题有待解决。

here你可以查看它是如何完成的,测试协程存在一些问题,你可以查看我的另一个问题here

我创建了一个 playground 来测试协程,它可能会有所帮助,您可以使用协程来测试问题,并使用 mockK 和协程测试来测试 another one

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-22
    • 2021-09-05
    • 2015-09-10
    • 2014-06-11
    • 2018-08-25
    • 2020-03-30
    • 1970-01-01
    • 2020-11-26
    相关资源
    最近更新 更多