I have a class that looks something like
class Foo {
private val scope = Job() + Dispatchers.IO
val emissions = PublishSubject.create<Bar>()
fun doSomething() {
scope.launch {
// Do a whole bunch of work...
withContext(Dispatchers.Main) emissions.onNext(bar)
}
}
}
And I'm trying to come up with a way to unit test it. I've tried making the scope injectable and writing something like
#Test fun testFoo() = runBlockingTest {
Dispatchers.setMain(TestCoroutineDispatcher())
val foo = Foo(this)
foo.doSomething()
foo.emissions.assertStuff()
}
but that doesn't seem to work. The assertion happens before the coroutine inside doSomething() has finished.
I've also tried making this dispatchers injectable, providing Dispatchers.Unconfined, but that didn't help either. Is there something wrong with this approach?
If you are able to expose the job for public API, you can try
class Foo {
private val scope: CoroutineScope = CoroutineScope(Job() + Dispatchers.IO)
val emissions = PublishSubject.create<Bar>()
fun doSomething() = scope.launch {
// Do a whole bunch of work...
withContext(Dispatchers.Main) { emissions.onNext(bar) }
}
}
class Test {
private val testFoo = Foo()
private val testObserver: TestObserver<Bar> = TestObserver.create()
#BeforeEach
fun setUp() {
testFoo.emissions.subscribe(testObserver)
}
#Test fun testFoo() {
runBlockingTest {
Dispatchers.setMain(TestCoroutineDispatcher())
testFoo.doSomething().join()
testObserver.assertValue(bar)
}
}
}
Testing asynchronous code can be done well with this library: Awaitility (or its kotlin extension)
You would write something like:
#Test fun testFoo() {
val foo = Foo()
foo.doSomething()
await().atMost(5, MILLIS).until(/* your check like - foo.emissions.onNext(bar) */);
}
Related
my android app need to call more than 10 APIs at the same time.
this call api's is use other library it made in other teams
and result receive by listener in JsonString format.
this multiple calling api is need to call at same time.
Because it takes a lot of time to call one API
i made it by callback structure like this.
but i hope refactor this code covert to coroutine.
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
private val listener = object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) {
when (apiName) {
"UserInfo" -> handleResultUserInfo(result)
"ProductInfo" -> handleResultProductInfo(result)
"....Info" -> handleResult___Info(result)
// ... and Others
}
}
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
fun start() {
callAPI("UserInfo")
callAPI("ProductInfo")
// ... and Others
}
fun callAPI(apiName: String, listener: ApiListener? = null) {
val listener = listener ?: this.listener
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
fun retry(apiName: String, listener: ApiListener) {
if (retryCount[apiName]!! < 3) {
retryCount[apiName]!!.plus(1)
library.callApi(apiName, listener)
}else{
throw RuntimeException("API Call Failed: $apiName")
}
}
fun handleResultUserInfo(result: String?) {
// TODO parse & do something
}
fun handleResultProductInfo(result: String?) {
// TODO parse & do something
}
fun handleResult___Info(result: String?) {
// TODO parse & do something
}
// ... and Others
i want use coroutine for readability not callback structure.
callback structure is not good method for readability i think.
so, i applied suspendCoroutine to library's listener for look like synchronous readability.
but, suspendCoroutine is suspend it functions when to until call it.resume
what is best practice in this case?
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
fun start(){
CoroutineScope(Dispatchers.IO).launch{
handleResultUserInfo(callAPI("UserInfo"))
handleResultProductInfo(callAPI("ProductInfo"))
handleResult___Info(callAPI("___Info"))
}
}
suspend fun callAPI(apiName: String, listener:ApiListener? = null) : String? = suspendCoroutine{
val listener = listener ?: object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) = it.resume(result)
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
↑ it waiting complete of previous work. it's not call api at same time
so i try to like this.
fun start(){
val callDataArr = arrayOf(
CallData("UserInfo", ::handleResultUserInfo),
CallData("ProductInfo", ::handleResultProductInfo),
CallData("___Info", ::handleResult___Info),
// ... and others
)
callDataArr.forEach {
CoroutineScope(Dispatchers.IO).launch{
it.handler(callAPI(it.apiName))
}
}
}
but... it doesn't look good.
because, CoroutineScope(Dispatchers.IO).launch called a lot of times
is not good for performance or have other problems?
I'm trying to write a component which uses different datasources of data.
Then data is combined and emitted in the different resulting flow.
class TaskControlComponent(
private val diskCacheDataSource: DiskCacheDataSource,
private val debugDataSource: DebugDataSource
) {
private val _localTasks = MutableStateFlow<Map<String, TaskItem>>(emptyMap())
val localTasks: StateFlow<Map<String, TaskItem>> = _localTasks
suspend fun loadLocal() {
flowOf(
diskCacheDataSource.defaultFeatures,
diskCacheDataSource.localFeatures,
debugDataSource.debugFeatures
).flattenMerge().collect {
computeLocalTasks()
}
}
private suspend fun computeLocalTasks() {
val resultTasks = HashMap<String, TaskItem>(64)
listOf(
diskCacheDataSource.defaultTasks,
diskCacheDataSource.localTasks,
debugDataSource.debugTasks
).forEach { tasksMap ->
tasksMap.value.forEach { entry ->
resultTasks[entry.key] = entry.value
}
}
_localTasks.emit(resultTasks)
}
}
DataSource
interface DiskCacheDataSource {
val defaultTasks: StateFlow<Map<String, TaskItem>>
val localTasks: StateFlow<Map<String, TaskItem>>
}
It works, but how to write junit test for that?
class TaskControlImplTest {
private lateinit var taskControl: TaskControlComponent
#Mock
lateinit var diskCacheDataSource: DiskCacheDataSource
#Mock
lateinit var debugDataSource: DebugDataSource
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
taskControl = TaskControlComponent(diskCacheDataSource, debugDataSource)
}
#Test
fun testFeatureControl() {
whenever(diskCacheDataSource.defaultTasks).thenReturn(
MutableStateFlow(
mapOf(
"1" to TaskItem(
"1",
TaskStatus.On
)
)
)
)
whenever(diskCacheDataSource.localTasks).thenReturn(MutableStateFlow(emptyMap()))
whenever(debugDataSource.debugTasks).thenReturn(MutableStateFlow(emptyMap()))
runBlocking {
taskControl.loadLocal()
}
runBlocking {
taskControl.localTasks.collect {
Assert.assertEquals(it.size, 1)
}
}
}
}
In case of the following sequence of commands
runBlocking {
taskControl.loadLocal()
}
runBlocking {
taskControl.localTasks.collect {
Assert.assertEquals(it.size, 1)
}
}
test freezes, and runs forewer.
When I swap the pieces of code, first instead of second and the contrary
runBlocking {
featureControl.localFeatures.collect {
Assert.assertEquals(it.size, 1)
}
}
runBlocking {
featureControl.loadLocal()
}
Tests finishes with warning
expected:<0> but was:<1>
Expected :0
Actual :1
Is it possible to write test for such usecase? What should be investigated or done in order to make test workable?
The reason the order matters here is because StateFlow is hot, unlike normal Flow, meaning it starts running with data immediately not when it is collected
I test with the turbine library but you don't need it. I don't remember the exact setup of not using turbine but it was a bit more complicated so I chose to use turbine
https://github.com/cashapp/turbine
I'm using "withTestAppliction" in one of my tests to test if the route works. Before all Tests the DB-Table "cats" should have no entries. To get the DAO I need Koin in this Test but if conflicts with "withTestAppliction" where Koin will also be startet and throws A KoinContext is already started
[Update]
I know I could use something like handleRequest(HttpMethod.Delete, "/cats") but I don't want to expose this Rest-Interface. Not even for testing.
#ExperimentalCoroutinesApi
class CatsTest: KoinTest {
companion object {
#BeforeClass
#JvmStatic fun setup() {
// once per run
startKoin {
modules(appModule)
}
}
#AfterClass
#JvmStatic fun teardown() {
// clean up after this class, leave nothing dirty behind
stopKoin()
}
}
#Before
fun setupTest() = runBlockingTest {
val dao = inject<CatDAO>()
dao.value.deleteAll()
}
#After
fun cleanUp() {
}
#Test
fun testCreateCat() {
withTestApplication({ module(testing = true) }) {
val call = createCat(predictName("Pepples"), 22)
call.response.status().`should be`(HttpStatusCode.Created)
}
}
}
fun TestApplicationEngine.createCat(name: String, age: Int): TestApplicationCall {
return handleRequest(HttpMethod.Post, "/cats") {
addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
setBody(listOf(
"name" to name,
"age" to age.toString()
).formUrlEncode())
}
}
After test (after withTestApplication()) call KoinContextHandler.get().stopKoin().
Example: https://github.com/comm1x/ktor-boot/blob/master/test/common/common.kt
It looks similar to the issue I faced. The problem was that the module() passed under the withTestApplication() was trying to create the Koin object again. I replaced the module() with specific modules that I had to load for the tests except for the Koin.
Refer - test sample and
application sample
Had the same problem executing multiple tests in a class. After removing the init/startKoin (since it's initialized in the Application when you test with the emulator).
I am not really sure if this is the correct approach, but it kind of works for me and my build server.
#ExperimentalCoroutinesApi
#RunWith(AndroidJUnit4ClassRunner::class) // or JUnit4..
class MyTest : KoinTest {
private val mockedAppModule: Module = module(override = true)
factory { myRepo }
}
#Before
fun setup() {
loadKoinModules(mockedAppModule)
}
#After
fun tearDown() {
unloadKoinModules(mockedAppModule)
}
#Test
fun testSubscriberRegistration() = runBlockingTest { // only needed if you are using supend functions
// test impl...
}
}
I would like to design an a service with the following API:
suspend fun getUsers(request: Request): List<User>
Under the hood I would send a request to the server (doesn't matter how, but lets say it's a reactive WebClient), but here's a trick: I can only send requests as often as every 500 ms, otherwise I will get an error.
Could someone recommend me how I could implement it such way that when I call getUsers from a coroutine it suspends, the unit of work is being added to some queue of the service that has this method, then implemented at some point in time and returned the result?
I assume I can use some ReceiveChannel as a queue, have a for loop for its elements with a delay inside, but I'm a bit lost where to put this logic. Should this be like a background method that will run forever and gets called by getUsers? Probably the close method will never be called, so this method can also be suspended, but how do I pass the value back from this infinite running method to getUsers that needs the results?
EDIT
At the moment I'm thinking of a solution like this:
private const val REQUEST_INTERVAL = 500
#Service
class DelayedRequestSenderImpl<T> : DelayedRequestSender<T> {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<T>> = Channel()
override suspend fun requestAsync(block: () -> T): Deferred<T> {
val deferred = GlobalScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred
}
#PostConstruct
private fun startRequestProcessing() = GlobalScope.launch {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < REQUEST_INTERVAL) {
delay(REQUEST_INTERVAL - diff)
lastRequestTime = now
}
request.start()
}
}
}
The problem I see here is that I have to generify the class to make the requestChannel generic, since the result of request may be anything. But this means that each instance of DelayedRequestSender will be tied to a particular type. Any advice on how to avoid this?
EDIT 2
Here's a refined version. The only possible flow that I see at the moment is that we have to make #PostConstruct method public in order to write any tests if we want or use reflection.
The idea was to not use GlobalScope and also have a separate Job for the processing method. Is this a fine approach?
interface DelayingSupplier {
suspend fun <T> supply(block: () -> T): T
}
#Service
class DelayingSupplierImpl(#Value("\${vk.request.interval}") private val interval: Int) : DelayingSupplier {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<*>> = Channel()
private val coroutineScope = CoroutineScope(EmptyCoroutineContext)
override suspend fun <T> supply(block: () -> T): T {
val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred.await()
}
#PostConstruct
fun startProcessing() = coroutineScope.launch(context = Job(coroutineScope.coroutineContext[Job])) {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < interval) {
delay(interval - diff)
}
lastRequestTime = LocalDateTime.now()
request.start()
}
}
}
I would recommend:
pushing your generics down to the function level
using an actor instead of your coroutine implementation (but possibly you prefer this).
Either way, this solution should let you use a single instance of your queue to handle the delay of all requests regardless of return type. (Apologies, I renamed some things to help my own conceptualization, hopefully this still makes sense):
private const val REQUEST_INTERVAL = 500
interface DelayedRequestHandler {
suspend fun <T> handleWithDelay(block: () -> T): T
}
class DelayedRequestHandlerImpl(requestInterval: Int = REQUEST_INTERVAL) : DelayedRequestHandler, CoroutineScope {
private val job = Job()
override val coroutineContext = Dispatchers.Unconfined + job
private val delayedHandlerActor = delayedRequestHandlerActor(requestInterval)
override suspend fun <T> handleWithDelay(block: () -> T): T {
val result = CompletableDeferred<T>()
delayedHandlerActor.send(DelayedHandlerMsg(result, block))
return result.await()
}
}
private data class DelayedHandlerMsg<RESULT>(val result: CompletableDeferred<RESULT>, val block: () -> RESULT)
private fun CoroutineScope.delayedRequestHandlerActor(requestInterval: Int) = actor<DelayedHandlerMsg<*>>() {
var lastRequestTime: LocalDateTime = LocalDateTime.now()
for (message in channel) {
try {
println("got a message processing")
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < requestInterval) {
delay(requestInterval - diff)
}
lastRequestTime = LocalDateTime.now()
#Suppress("UNCHECKED_CAST")
val msgCast = message as DelayedHandlerMsg<Any?>
val result = msgCast.block()
println(result)
msgCast.result.complete(result)
} catch (e: Exception) {
message.result.completeExceptionally(e)
}
}
}
fun main() = runBlocking {
val mydelayHandler = DelayedRequestHandlerImpl(2000)
val jobs = List(10) {
launch {
mydelayHandler.handleWithDelay {
"Result $it"
}
}
}
jobs.forEach { it.join() }
}
So this is the final implementation I came up with. Note the SupevisorJob as we don't want the processing to stop if one of requests fails, which is totally possible and fine (in my case at least).
Also, the option suggested by #Laurence might be better, but I decided to not use actors for now due to API being marked as obsolete.
#Service
class DelayingRequestSenderImpl(#Value("\${vk.request.interval}") private val interval: Int) : DelayingRequestSender {
private var lastRequestTime: LocalDateTime = LocalDateTime.now()
private val requestChannel: Channel<Deferred<*>> = Channel()
//SupervisorJob is used because we want to have continuous processing of requestChannel
//even if one of the requests fails
private val coroutineScope = CoroutineScope(SupervisorJob())
override suspend fun <T> request(block: () -> T): T {
val deferred = coroutineScope.async(start = CoroutineStart.LAZY) { block() }
requestChannel.send(deferred)
return deferred.await()
}
#PostConstruct
fun startProcessing() = coroutineScope.launch {
for (request in requestChannel) {
val now = LocalDateTime.now()
val diff = ChronoUnit.MILLIS.between(lastRequestTime, now)
if (diff < interval) {
delay(interval - diff)
}
lastRequestTime = LocalDateTime.now()
request.start()
}
}
}
I tried to create Unit Test using Rxjava + Retrofit but it always give an error.
I have tried all tutorials and reference related of my questions. I did success when create an unit test of other method (other case), but failed in this case (Rx + retrofit).
Request Data Code:
fun getDetailEvent(idEvent: String?) {
view.showLoading()
apiService.getDetailEvent(idEvent)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
val compositeDisposable: CompositeDisposable? = null
compositeDisposable?.add(it)
}
.doFinally { view.hideLoading() }
.subscribe({
val listModel = it
if (listModel != null) {
view.onDetailEventLoaded(listModel)
} else {
view.onDetailEventLoadFailed("Empty or Error List")
}
},
{
val errorMessage = it.message
if (errorMessage != null) {
view.onDetailEventLoadFailed(errorMessage)
}
})
}
Unit Test Code :
class DetailNextMatchPresenterTest {
#Mock
private lateinit var view : DetailNextMatchView
#Mock
private lateinit var apiService: ApiService
private lateinit var presenter: DetailNextMatchPresenter
#Before
fun setup(){
MockitoAnnotations.initMocks(this)
presenter = DetailNextMatchPresenter(view, apiService)
}
#Test
fun getDetailEvent() {
val event : MutableList<EventModel> = mutableListOf()
val response = ResponseEventModel(event)
val idEvent = "44163"
`when`(apiService.getDetailEvent(idEvent)
.test()
.assertSubscribed()
.assertValue(response)
.assertComplete()
.assertNoErrors()
)
presenter.getDetailEvent(idEvent)
verify(view).showLoading()
verify(view).onDetailEventLoaded(response)
verify(view).hideLoading()
}
}
I appreciate all suggestion. Thanks
I believe that the issue is that you haven't forced your code to behave synchronously in the context of your test, so the Observable runs in parallel to your test. Try adding this in your setup method:
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } If you're using RxJava2. Try looking for a similar method if you're using RxJava 1.