Skip to content
This repository has been archived by the owner on May 16, 2024. It is now read-only.

Commit

Permalink
Added helper functions for testing coroutines. Fixed problem with del…
Browse files Browse the repository at this point in the history
…ay in coroutines.
  • Loading branch information
vovahost authored and johsoe committed Feb 7, 2019
1 parent 5c9092a commit 7022afb
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dk.nodes.arch.presentation.base

import androidx.lifecycle.Lifecycle
import kotlin.coroutines.CoroutineContext

interface BasePresenter<in V> {

Expand All @@ -17,4 +18,6 @@ interface BasePresenter<in V> {
fun onStop()

fun onViewDetached()

fun activateTestMode(context: CoroutineContext)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ package dk.nodes.arch.presentation.base
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Delay
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.runBlocking
import java.util.concurrent.LinkedBlockingQueue
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume

abstract class BasePresenterImpl<V> : BasePresenter<V>, LifecycleObserver {

Expand Down Expand Up @@ -101,19 +106,12 @@ abstract class BasePresenterImpl<V> : BasePresenter<V>, LifecycleObserver {
view?.let(block) ?: cachedViewActions.add(Runnable { view?.block() })
}

fun activateTestMode() {
val mainThreadSurrogate = newSingleThreadContext("Single thread context")
mainCoroutineContext = mainThreadSurrogate + job
ioCoroutineContext = mainThreadSurrogate + job
defaultCoroutineContext = mainThreadSurrogate + job
}

fun launchOnUI(block: suspend CoroutineScope.() -> Unit): Job {
return mainScope.launch(context = mainCoroutineContext, block = block)
}

fun launchOnIO(block: suspend CoroutineScope.() -> Unit): Job {
return ioScope.launch(context = mainCoroutineContext, block = block)
return ioScope.launch(context = ioCoroutineContext, block = block)
}

fun launch(block: suspend CoroutineScope.() -> Unit): Job {
Expand All @@ -132,4 +130,36 @@ abstract class BasePresenterImpl<V> : BasePresenter<V>, LifecycleObserver {
return defaultScope.async(context = defaultCoroutineContext, block = block)
}

override fun activateTestMode(context: CoroutineContext) {
mainCoroutineContext = context + job
ioCoroutineContext = context + job
defaultCoroutineContext = context + job
}

}

@InternalCoroutinesApi
fun <T> runBlockingTest(presenter: BasePresenter<*>, block: suspend CoroutineScope.() -> T): T {
return runBlocking(TestContext()) {
presenter.activateTestMode(this.coroutineContext)
return@runBlocking block.invoke(this)
}
}

/**
* Temporary workaround which sets delays used in Coroutines to 0
* Can be replaced with Kotlin built-in solution when https://github.com/softagram/kotlinx.coroutines/pull/3 will be merged
* For more info see: https://github.com/Kotlin/kotlinx.coroutines/issues/541
* https://stackoverflow.com/a/49078296/1502079
* */
@InternalCoroutinesApi
class TestContext : CoroutineDispatcher(), Delay {

override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
continuation.resume(Unit)
}

override fun dispatch(context: CoroutineContext, block: Runnable) {
block.run() // dispatch on calling thread
}
}
51 changes: 51 additions & 0 deletions app/src/test/java/dk/nodes/arch/CoroutineTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dk.nodes.arch

import androidx.lifecycle.Lifecycle
import dk.nodes.arch.presentation.base.BasePresenterImpl
import dk.nodes.arch.presentation.base.BaseView
import dk.nodes.arch.presentation.base.runBlockingTest
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.delay
import org.junit.Test

class CoroutineTest {

private class View: BaseView {
fun showData() {
println("Showing data")
}
}

private class Presenter: BasePresenterImpl<View>() {

fun loadData() {
println("loadData start in thread ${Thread.currentThread().name}")
launchOnUI {
println("loadData before delay in thread ${Thread.currentThread().name}")
delay(1000)
println("loadData after delay in thread ${Thread.currentThread().name}")
view { showData() }
println("loadData after view in thread ${Thread.currentThread().name}")
}
println("loadData end in thread ${Thread.currentThread().name}")
}
}

@InternalCoroutinesApi
@Test
fun `Test run blocking with context launches coroutine on same thread`() {
val viewMock = mockk<View>(relaxed = true)
val lifecycleMock = mockk<Lifecycle>(relaxed = true)
val presenter = Presenter()

presenter.onViewCreated(viewMock, lifecycleMock)

runBlockingTest(presenter) {
presenter.loadData()
}

verify { viewMock.showData() }
}
}
32 changes: 0 additions & 32 deletions app/src/test/java/dk/nodes/arch/ExampleUnitTest.kt

This file was deleted.

0 comments on commit 7022afb

Please sign in to comment.