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)
}
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>()
// ...
Related
We have some code to read data from DynamoDB:
suspend fun getKeys(owner: String): Set<String> {
...
val query = ...
query.subscribe { page -> foo(page) }.await()
return ...
}
The subscribe{} above is defined in AWS SDK:
default CompletableFuture<Void> subscribe(Consumer<T> consumer)
I'd like to unit test the logic in this function, for subscribe I don't care about it, hopefully just mock and do nothing.
I tried mock a callback in my unit test (irrelevant code removed):
class BucketRepoTest(
#Mock private val query: SdkPublisher<Page<DbBucket>>
) {
#Test
fun `get keys should be working`() {
val callback = mock<(Page<DbBucket>) -> Unit>()
val result = mock<CompletableFuture<Void>>()
whenever(query.subscribe(callback)).thenReturn(result)
runBlocking {
val keys = data.getKeys("Charlie")
assert(keys.isEmpty())
}
}
}
But when I run the test I got NPE:
query.subscribe { page -> foo(page) } must not be null
java.lang.NullPointerException: query.subscribe { page -> foo(page) } must not be null
at com.myApp.getKeys(myfile.kt:75)
at ...
Any idea how to fix it?
The result cannot be void.
val result = mock<CompletableFuture>()
try to create a mock data and pass as result
val result = the object or data type you want to more
Example
data Result( Charlie = "john", age = "23")
then
class BucketRepoTest(
#Mock private val query: SdkPublisher<Page<DbBucket>> ) {
#Test
fun `get keys should be working`() {
val callback = mock<(Page<DbBucket>) -> Unit>()
val result = Result()
whenever(query.subscribe(callback)).thenReturn(result)
runBlocking {
val keys = data.getKeys("Charlie")
assert(keys.isEmpty())
}
}
}
I have the following code (in Kotlin):
class X {
fun foo() {
val A(1, true, "three")
val b = B()
b.bar(A)
}
}
What I want to to is find out what A has been instantiated with.
My test code looks like so:
// Needed for something else
every { anyConstructed<A>().go() } returns "testString"
// What I'm using to extract A
val barSlot = slot<A>()
verify { anyConstructed<B>().bar(capture(barSlot)) }
val a = barSlot.captured
How can I check what values A has been instantiated with now I've managed to capture the mock that was created when it was constructed (thanks to the every statement)?
Thanks!
You can do it in two ways:
Using slot to capture the parameter:
#Test
fun shouldCheckValuesAtConstruct() {
val a = A(1, true, "s")
val b = mockk<B>()
val aSlot = slot<A>()
every { b.bar(a = capture(aSlot)) } returns Unit
b.bar(a)
val captured = aSlot.captured
assertEquals(1, captured.a)
assertEquals(true, captured.b)
assertEquals("s", captured.s)
}
Or using withArg function and inline assertions
#Test
fun shouldCheckValuesAtConstructInlineAssertion() {
val a = A(1, true, "s")
val b = mockk<B>()
every { b.bar(a) } returns Unit
b.bar(a)
verify {
b.bar(withArg {
assertEquals(1, it.a)
assertEquals(true, it.b)
assertEquals("s", it.s)
})
}
}
using kotlin, having code
fun fetchRemoteDataApi(): Single<RemoteDataResponse> = networkApi.getData()
// it is just a retrofit
#GET(".../api/getData")
fun getData() : Single<RemoteDataResponse>
fun mergeApiWithDb(): Completable = fetchRemoteDataApi()
.zipWith(localDao.getAll())
.flatMapCompletable { (remoteData, localData) ->
doMerge(remoteData, localData) //<== return a Completable
}
the code flow:
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache() //<=== would like do some inspection at this level
PublishSubject.create<Unit>().toFlowable(BackpressureStrategy.LATEST)
.compose(Transformers.flowableIO())
.switchMap {
//merge DB with api, or local default value first then listen to DB change
mergeApiDbCall.andThen(listAllTopics())
.concatMapSingle { topics -> remoteTopicUsers.map { topics to it } }
}
.flatMapCompletable { (topics, user) ->
// do something return Completable
}
.subscribe({
...
}, { throwable ->
...
})
and when making the call
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache()
the question is if would like to inspect on the Singles<RemoteDataResponse> returned from fetchRemoteDataApi() (i.e. using Log.i(...) to printout the content of RemoteDataResponse, etc.), either in got error or success case, how to do it?
/// the functions
fun listAllTopics(): Flowable<List<String>> = localRepoDao.getAllTopics()
// which a DAO:
#Query("SELECT topic FROM RemoteDataTable WHERE read = 1")
fun getAllTopics(): Flowable<List<String>>
///
private val remoteTopicUsers: Single<List<User>>
get() {
return Single.create {
networkApi.getTopicUsers(object : ICallback.IGetTopicUsersCallback {
override fun onSuccess(result: List<User>) = it.onSuccess(result)
override fun onError(errorCode: Int, errorMsg: String?) = it.onError(Exception(errorCode, errorMsg))
})
}
}
You cannot extract information about elements from the Completable. Though you can use doOnComplete() on Completable, it will not provide you any information about the element.
You can inspect elements if you call doOnSuccess() on your Single, so you need to incorporate this call earlier in your code. To inspect errors you can use doOnError() on both Completable or Single.
I am trying to implement a QueryBus. Basically, I want to register a list of QueryHandlers. Each QueryHandler implements a handle method defined by an interface. Each QueryHandler is associated to a Query. I want to be able to retrieve a QueryHandler using the Query and call handle on it.
The thing is the handle has to be generic because each QueryHandler handles a Query differently. They all take a dedicated Query and may return whatever they want.
interface Query<R>
interface QueryHandler<R, Q : Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
class ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
class GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = mapOf(
with(ListBooksQueryHandler()) {this.listenTo() to this},
with(GetBookQueryHandler()) {this.listenTo() to this}
)
val command = ListBooksQuery()
val result = queryHandlers[command::class.toString()].handle(command)
// Should print the list of BookDto
print(result)
}
I don't even know if its possible, to be honest.
UPDATE 1:
I changed the usage example in the main to show what I am really trying to do. The List was for (bad?) demonstration purpose. I want to store the QueryHandlers and retrieve them from a map.
Additional resources:
Here is what I really want to do:
https://gist.github.com/ValentinTrinque/76b7a32221884a46e657090b9ee60193
UPDATE I've read your gist and tried to come up with a solution that will provide a clean interface to the user of the QueryBusMiddleware.
Note that I used objects instead of classes for the QueryHandler implementations, which felt more natural to me (since there is only one possible entry in the map for each Query implementation).
interface Query<R>
interface QueryHandler<R, Q: Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
object ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
object GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = listOf(
ListBooksQueryHandler,
GetBookQueryHandler
)
val dispatcher: QueryBusMiddleware = QueryDispatcherMiddleware(queryHandlers)
// Calling query bus
val query = ListBooksQuery()
// Result should be List<BookDto>
val result = dispatcher.dispatch(query)
print(result)
}
interface QueryBusMiddleware {
fun <R, Q : Query<R>> dispatch(query: Q): R
}
class QueryDispatcherMiddleware constructor(handlers: List<QueryHandler<*, *>>) : QueryBusMiddleware {
private val handlers = HashMap<String, QueryHandler<*, *>>()
init {
handlers.forEach { handler -> this.handlers[handler.listenTo()] = handler }
}
override fun <R, Q : Query<R>> dispatch(query: Q): R {
val queryClass = query::class.toString()
val handler = handlers[queryClass] ?: throw Exception("No handler listen to the query: $queryClass")
return handler::class.members.find { it.name == "handle" }!!.call(handler, query) as R
}
}
I'm wondering if it's possible in Kotlin to deserialize (restore property values) of a declared object, without having to manually assign the properties or resorting to reflection. The following snippet further explains:
object Foo: Serializable {
var propOne: String = ""
// ...
fun persist() {
serialize(this)
// no problem with serialization
}
fun restore(bytes: ByteArray) {
val fooObj: Foo = deserialize(bytes) as Foo
// It seems Kotlin allows us to use singleton as type!
// obvioulsly either of the following is wrong:
// this = fooObj
// Foo = fooObj
// ... is there a way to 'recover' the singleton (object) other than
// manual assignment of properties (or reflection) ???
}
}
There is no way to reassign the global reference to a singleton with a new instance. At most you can write out the properties during serialization, and then on deserialization directly read the properties and mutate the state in the original object. It will require custom code for you to assign the properties into the object either by direct assignment or reflection. It would be better if you make your own singleton mechanism that holds an instance that you can swap out to be another instance that you deserialize.
have faced with the same issue, and want to share with you my solution:
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import java.io.File
import java.lang.reflect.Modifier
typealias ObjMap = HashMap<String, Any?>
fun <T : Any> T.getInstance() : Any? {
val target = if(this is Class<*>) this else javaClass
return target.getDeclaredField("INSTANCE")?.get(null)
}
class ObjectHelper {
companion object {
val mapper = ObjectMapper().apply {
enable(SerializationFeature.INDENT_OUTPUT)
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
fun objectToMap(obj: Any): ObjMap {
var res = ObjMap()
val instance = obj.getInstance()
val o = instance ?: obj
o.javaClass.declaredFields.forEach {
if(it.name != "INSTANCE") {
it.isAccessible = true
val value = if(Modifier.isStatic(it.modifiers)) it.get(null) else it.get(o)
res[it.name] = value
}
}
o.javaClass.classes.forEach {
res[it.simpleName] = objectToMap(it)
}
return res
}
fun saveObject(path: String, obj: Any) {
mapper.writeValue(File(path), objectToMap(obj))
}
fun loadObject(path: String, obj: Any) {
val json = mapper.readValue<HashMap<*,*>>(File(path), HashMap::class.java) as ObjMap
loadObject(obj, json)
}
fun loadObject(obj: Any, props: ObjMap) {
val objectParam = mapper.writeValueAsString(props)
mapper.readValue(objectParam, obj::class.java)
obj.javaClass.classes.forEach {
val instance = it.getInstance()
val map = props[it.simpleName]
if(map != null && instance != null) {
loadObject(instance, map as ObjMap)
}
}
}
}
}
Usage example:
object TestObj {
var f1: String = "f1"
var f2: String = "f2"
object TestObj_2 {
var f1: String = "f1_1"
var f2: String = "f2_2"
}
}
TestObj.f1 = "aaa"
saveObject("out.json", TestObj)
TestObj.f1 = "bbb"
loadObject("out.json", TestObj)
println(TestObj.f1)
Result will be "aaa".