简体   繁体   中英

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

I am investigation the MockK library with my Android JUnit tests

testImplementation "io.mockk:mockk:1.10.0"

I have an issue when attempting to spyk on suspend functions

heres my Junit test

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

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

@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 ? How do I coVerify that suspend functions have been called?

UPDATE

I believe the issue is caused by the fact I am using Room database. My dao interface method reset() is implemented by room generated code as

 @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.

Is it possible to match this generated version of public Object reset(final Continuation<? super Unit> p0) ?

Is this a more basic issue with mockk that it cannot mockk java classes? Or Java implementations of Kotlin interfaces?

UPDATE 2

When my Room DAO functions are not suspend then Mockk works as required

using these dummy functions in my 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.

To test with suspending functions with scope you might need to use

launch builder and advance time until idle, or for a time period for testing the progress, like it's done with RxJava counter parts.

I had the same issue with MockWebServer, here you can check out the question .

  launch {
         dao.delete()
    }

    advanceUntilIdle()

And use Coroutine rule with tests to have same scope for each operation.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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