简体   繁体   English

是否可以使用 MockK 监视暂停 Android Room DAO 功能

[英]Is it possible to spy on suspend Android Room DAO functions with MockK

I am investigation the MockK library with my Android JUnit tests我正在用我的 Android JUnit 测试调查 MockK 库

testImplementation "io.mockk:mockk:1.10.0"

I have an issue when attempting to spyk on suspend functions尝试监视挂起功能时遇到问题

heres my Junit test这是我的 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()
            }
        }
    }

This test throws an java.lang.AssertionError at dao.reset() as follows:-该测试在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: -

My dao reset() method resembles this:-我的 dao reset() 方法类似于:-

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

Why am I seeing this java.lang.AssertionError ?为什么我看到这个java.lang.AssertionError How do I coVerify that suspend functions have been called?如何coVerify已调用挂起函数?

UPDATE更新

I believe the issue is caused by the fact I am using Room database.我相信这个问题是由我使用 Room 数据库引起的。 My dao interface method reset() is implemented by room generated code as我的 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);
  }

which means the coVerify{} is matching this function and not my interface version.这意味着 coVerify{} 匹配此 function 而不是我的接口版本。

Is it possible to match this generated version of public Object reset(final Continuation<? super Unit> p0) ?是否可以匹配这个生成的public Object reset(final Continuation<? super Unit> p0)版本?

Is this a more basic issue with mockk that it cannot mockk java classes?这是 mockk 的一个更基本的问题,它不能模拟 java 类吗? Or Java implementations of Kotlin interfaces?还是 Kotlin 接口的 Java 实现?

UPDATE 2更新 2

When my Room DAO functions are not suspend then Mockk works as required当我的 Room DAO 功能未挂起时,Mockk 将按要求工作

using these dummy functions in my DAO:-在我的 DAO 中使用这些虚拟函数:-

@Transaction
fun experimentation() {
    experiment()
}

@Transaction
fun experiment() {
    experimental()
}

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

My test passes我的测试通过了

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

    dao.experimentation()

    verify { dao.experiment() }
}

When I change my dummy functions as follows the test still passes当我如下更改我的虚拟函数时,测试仍然通过

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

@Transaction
fun experiment() {
    experimental()
}

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

However when I change my dummy functions as follows the test throws an exception但是,当我如下更改我的虚拟函数时,测试会引发异常

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

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

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

The failing tests resembles this:-失败的测试类似于:-

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

    dao.experimentation()

    coVerify { dao.experiment() }

}

The exception is例外是

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: -

There might be nothing wrong with spy but the asynchronous nature of the transaction function you are invoking. spy可能没有任何问题,但您正在调用的事务 function 的异步性质。

To test with suspending functions with scope you might need to use要使用 scope 测试挂起功能,您可能需要使用

launch builder and advance time until idle, or for a time period for testing the progress, like it's done with RxJava counter parts. launch构建器并提前时间直到空闲,或者测试进度的一段时间,就像使用 RxJava 计数器部件一样。

I had the same issue with MockWebServer, here you can check out the question .我对 MockWebServer 也有同样的问题,你可以在这里查看问题

  launch {
         dao.delete()
    }

    advanceUntilIdle()

And use Coroutine rule with tests to have same scope for each operation.并使用带有测试的协程规则,使每个操作具有相同的 scope。

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

}

You can use rule as below您可以使用如下规则

 testCoroutineRule.runBlockingTest {

           dao.delete()

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

        }

Also you can try putting dao.delete() in launch .您也可以尝试将 dao.delete() 放入launch In some tests it did not work without launch while some other work without it and even some of them are flakky with everything i tried.在某些测试中,如果没有启动它就无法工作,而在其他一些测试中,如果没有它,甚至有些测试对我尝试的所有东西都很不稳定。 There are some issues with coroutines-test to be solved. coroutines-test 有一些问题需要解决。

here you can check how it's done and there are some issues with test-coroutines, you can check out my other question here . 在这里您可以检查它是如何完成的,并且测试协程存在一些问题,您可以在这里查看我的其他问题。

I created a playground to test coroutines, it might helpful and you can test out the issues with coroutines, and another one with mockK and coroutines tests.我创建了一个测试协程的游乐场,它可能会有所帮助,您可以使用协程测试问题, 另一个使用 mockK 和协程测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM