Skip to content

Instantly share code, notes, and snippets.

@alvindizon
Last active September 6, 2024 18:09
Show Gist options
  • Save alvindizon/227735c2e21763368cc248912476cf0f to your computer and use it in GitHub Desktop.
Save alvindizon/227735c2e21763368cc248912476cf0f to your computer and use it in GitHub Desktop.
JUnit extensions for LiveData(InstantExecutor), RxJava2, and Coroutines
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
@ExperimentalCoroutinesApi
class CoroutineExtension(
private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : BeforeEachCallback, AfterEachCallback, TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(dispatcher)
}
override fun afterEach(context: ExtensionContext?) {
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
import android.annotation.SuppressLint
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
@SuppressLint("RestrictedApi")
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
@SuppressLint("RestrictedApi")
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
class TestSchedulerExtension : BeforeTestExecutionCallback, AfterTestExecutionCallback {
override fun beforeTestExecution(context: ExtensionContext?) {
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() }
}
override fun afterTestExecution(context: ExtensionContext?) {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
@ExperimentalCoroutinesApi
@ExtendWith(value = [InstantExecutorExtension::class, RxSchedulerExtension::class, CoroutineExtension::class])
class SampleViewModelTest {
....
}
@alvindizon
Copy link
Author

the CoroutineExtension is outdated and there's probably no need to use it with Coroutines 1.6. See https://medium.com/androiddevelopers/migrating-to-the-new-coroutines-1-6-test-apis-b99f7fc47774 for a great rundown of how to migrate to the new Coroutines 1.6 unit test APIs

@neboskreb
Copy link

Repo instant-task-executor-extension provides a packaged version of this InstantExecutorExtension.
No need to copy the code anymore, now include it with just one line:

dependencies {
    testImplementation 'io.github.neboskreb:instant-task-executor-extension:1.0.0'
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment