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())
}
}
}
Related
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>()
// ...
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.
Say I have this situation:
interface Repository {
fun getMovies(success: (List<String>) -> Unit, failure: (Int) -> Unit)
}
and I want to mock the implementation of this interface. Basically in this case, there are two lambdas as input parameters to the getmovie method, and for the test case, I only want to produce success (success.invoke(theMoviesList) should be called).
Below is something similar to what I would like to do:
class MovieViewModel constructor(val repository: AppRepository) {
var movieNames = listOf<String>() // Not private, or live data, for simplicity
fun fetchMovies() {
repository.fetchMovies(
success = {
movies ->
this.movieNames = movies
}}, failure: {
statusCode ->
})
}
}
class MoviePageTests {
private var movieViewModel: MovieViewModel? = null
#Mock
lateinit var mockRepository: AppRepository
#Before
#Throws(Exception::class)
fun before() {
MockitoAnnotations.initMocks(this)
movieViewModel = MovieViewModel(repository = mockRepository)
}
#Test
fun checkFetchMoviesUpdatesMoviesData() {
var testMovies = listof("Dracula", "Superman")
//Set up mockito so that the repository generates success with testMovies above
?????
//
movieViewModel.fetchMovies()
assertEquals(movieViewModel.movies, testMovies)
}
}
I know how to do this by way of a RepositoryImpl, but not in Mockito, despite looking at many examples online.
Any ideas?
all! I want to get data in DB at first and than call server if DB is empty. But I don't have any response when I use this way. I tried to call server at first and it was successful. Whats wrong??? This is my code:
private fun getDataFromRepository() {
val subscription =
carRepository.getCars()!!.
subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread()).
subscribe(
{ cars ->
LOG.info(cars.size.toString())
carRepository.saveCarsInDB(cars)
data.postValue(cars)
},
{ e ->
loadError.postValue(e.toString())
LOG.warning(e.toString())
})
subscriptions.add(subscription)
}
Flowables:
fun getCars(): Single<List<Car>>? {
val db = getDataFromDB()
val server = getDataFromServerFlowable()
val mock = getDataFromMock()
return Flowable.concat(db, server).first(mock)
}
private fun getDataFromServerFlowable(): Flowable<List<Car>> {
return carApi.getPostsFlowable()
}
private fun getDataFromDB(): Flowable<List<Car>> {
return RealmCar().queryAllAsFlowable() //"com.github.vicpinm:krealmextensions:2.4.0"
.map { cars -> mapper.convertListRealmCarToListCar(cars) }
.filter { car -> car.isNotEmpty()}
}
private fun getDataFromMock(): List<Car> {
val cars: MutableList<Car> = mutableListOf()
val car = Car(0, 0, "Test", "Test", "Test")
cars.add(car)
return cars
}
Server call:
#GET("/photos")
fun getPostsFlowable(): Flowable<List<Car>>
Depending on your logic you should consider using merge instead of concat to interleave the elements. In your case getDataFromDB() is not emitting, so the final Flowable is waiting for it before emitting getDataFromServerFlowable(), There are plenty of good answers of merge vs concat (i.e this one)
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
}
}