'How we can mock a CoroutineDatabase in ktor?

I use the KMongo tool How we can mock a Coroutine Database? How can we mock our database in a koin module?

Is there a way to do this? Thanks for guiding me

Methods I have tried and it has not worked:

The first method:

  single<CoroutineDatabase> {
      val client = Mockito.mock(CoroutineClient::class.java)
      client.getDatabase(CoreConstants.DATABASE_NAME)
  }

The second method:

    single<CoroutineDatabase> {
        val client = declareMock<CoroutineClient> {  }
        client.getDatabase(CoreConstants.DATABASE_NAME)
    }


Solution 1:[1]

I've managed to get this working with MockK with the following approach.


TLDR

Just use a mock of MongoDatabase/MongoCollection<T> and make their coroutine extension property return a mocked CoroutineDatabase/CoroutineCollection<T>. Also need to mock the actual MongoDatabase::getCollection to return the respective MongoCollection<T>.


Suppose we have this scenario.

data class User(val id: Int, val name: String)

class Service(private val myDatabase: CoroutineDatabase) {
    private val userCollection: CoroutineCollection<User> = myDatabase.getCollection("users")
    suspend fun getById(id: Int): User? = userCollection.findOneById(id)
}

Since userCollection is acquired by calling the inline method CoroutineDatabase::getCollection we need to mock all the code inside that inline because inline methods cannot be mocked with MockK (at the time of writing). Looking at the method code

inline fun <reified TDocument : Any> getCollection(
        collectionName: String = KMongoUtil.defaultCollectionName(TDocument::class)
    ): CoroutineCollection<TDocument> =
        database.getCollection(collectionName, TDocument::class.java).coroutine

It just calls com.mongodb.reactivestreams.client.MongoDatabase::getCollection and then uses this extension property to map it to a CoroutineCollection. Notice it uses the field database from CoroutineDatabase which is a MongoDatabase (The CoroutineDatabase was previously obtain via a similar extension property for MongoDatabase).

val <T : Any> MongoCollection<T>.coroutine: CoroutineCollection<T> get() = CoroutineCollection(this)

val MongoDatabase.coroutine: CoroutineDatabase get() = CoroutineDatabase(this)

Having all of this we need to mock:

  • Both coroutine extension properties on MongoDatabase and MongoCollection<T> (see mocking extension properties with MockK)
  • The actual MongoDatabase::getCollection because CoroutineDatabase::getCollection is an inline function
// Arrange
val mockedMongoDd: MongoDatabase = mockk<MongoDatabase> {
    mockkStatic(MongoDatabase::coroutine)
    val that = this
    every { coroutine } returns mockk {
        every { database } returns that
    }
}

val mockedMongoCol: MongoCollection<User> = mockk<MongoCollection<User>> {
    mockkStatic(MongoCollection<T>::coroutine)
    val that = this
    every { ofType<MongoCollection<T>>().coroutine } returns mockk {
        every { collection } returns that
    }
}

every {
    mockedMongoDb.getCollection("users", User::class.java)
} returns mockedMongoCol

val mockedCoroutineDb = mockedMongoDb.coroutine
val mockedCoroutineCol = mockedMongoCol.coroutine

val service = Service(mockedCoroutineDb)
val expectedUser = User(2, "Joe")

coEvery {
    mockedCoroutineCol.findOneById(2)
} returns expectedUser

// Act
val actualUser = service.getById(2)

// Assert
assertEquals(expectedUser, actualUser)

Finally, one could make some methods like the following to hide this details from the test.

inline fun <reified T : Any> mockkCoroutineCollection(
    name: String? = null,
    relaxed: Boolean = false,
    vararg moreInterfaces: KClass<*>,
    relaxUnitFun: Boolean = false,
    block: MongoCollection<T>.() -> Unit = {}
): MongoCollection<T> = mockk(name, relaxed, *moreInterfaces, relaxUnitFun = relaxUnitFun) {
    mockkStatic(MongoCollection<*>::coroutine)
    val that = this
    every { coroutine } returns mockk(name, relaxed, *moreInterfaces, relaxUnitFun = relaxUnitFun) {
        every { collection } returns that
    }
    block()
}

inline fun mockkCoroutineDatabase(
    name: String? = null,
    relaxed: Boolean = false,
    vararg moreInterfaces: KClass<*>,
    relaxUnitFun: Boolean = false,
    block: MongoDatabase.() -> Unit = {}
): MongoDatabase = mockk(name, relaxed, *moreInterfaces, relaxUnitFun = relaxUnitFun) {
    mockkStatic(MongoDatabase::coroutine)
    val that = this
    every { coroutine } returns mockk(name, relaxed, *moreInterfaces, relaxUnitFun = relaxUnitFun) {
        every { database } returns that
    }
    block()
}

This would reduce the first lines to

val mockedMongoDb: MongoDatabase = mockkCoroutineDatabase()
val mockedMongoCol: MongoCollection<User> = mockkCoroutineCollection<User>()
// ...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1