-
Notifications
You must be signed in to change notification settings - Fork 0
Using TestCoroutineDispatcher
Devrath edited this page Jul 3, 2021
·
16 revisions
- This is a type of co-routine dispatcher that is used to test the coroutines.
class TestCoroutineDispatcher : CoroutineDispatcher, Delay, DelayController- As seen above the
testCoroutineDispatcherextends the classescoroutineDispatcher,delay,delayController. - It performs immediate and lazy execution of co-routines.
- By default
testCoroutineDispatcheris immediate meaning any tasks that are scheduled to run immediately is executed immediately - If there is a delay the virtual clock is advanced by the amount of delay involved.
- Coroutines provides an easy way and elegant way of executing the asynchronous code. But sometimes coroutines are hard to unit test.
- Most important thing to remember here is, to understand how to build a coroutine in unit test and when executing that coroutine in the unit test, we need to understand how to wait for all the jobs in the unit test to complete before completing the test function.
- Next in the task is we want to run our tests as fast as possible without waiting for the delay in coroutines to finish.
ext {
coroutines = "1.3.1"
}
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
// testImplementation for pure JVM unit tests
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
// androidTestImplementation for Android instrumentation tests
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
}- This class extends TextWatcher
- Define it in the util package in the
testfolder
@ExperimentalCoroutinesApi
class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}- Above rule takes care of watching the tests
startingand thefinishing. - There is a reference to
testDispatcher. - As the tests start and finish, this replaces
Dispatchers.Mainwith ourtestDispatcher.
@ExperimentalCoroutinesApi
class HeavyWorkerTest {
@get:Rule
var coroutinesTestRule = CoroutineTestRule()
// Some tests written
}-
Heavy Workerclass has a suspend functionheavyOperation()that does the heavy lifting. - We should try to write a test function for this that is testable and executes quickly.
class HeavyWorker {
suspend fun heavyOperation(): Long {
return withContext(Dispatchers.Default) {
return@withContext doHardMaths()
}
}
// waste some CPU cycles
private fun doHardMaths(): Long {
var count = 0.0
for (i in 1..100_000_000) {
count += sqrt(i.toDouble())
}
return count.toLong()
}
}- We have three options to choose for testing the suspend function
kotlinx.coroutines.runBlocking ❌kotlinx.coroutines.test.runBlockingTest ❌kotlinx.coroutines.test.TestCoroutineDispatcher.runBlockingTest
when using kotlinx.coroutines.runBlocking
@Test
fun `testing using the run blocking`() {
val heavyWorker = HeavyWorker()
val expected = 666666671666
val result = heavyWorker.heavyOperation()
assertEquals(expected, result)
}---> This will pass for sure but it will take time to pass .... But most importantly it passes. Say the function we are testing has an additional delay of say 50 seconds
suspend fun heavyOperation(): Long {
return withContext(Dispatchers.Default) {
delay(50_000) // ----------------------------- > Here is the delay
return@withContext doHardMaths()
}
}---> Being said it passes but the test will wait for 50_000 delay and then finishes, it's like adding overhead on top of an existing thing
when using kotlinx.coroutines.test.runBlockingTest
@Test
fun useRunBlockingTest() = runBlockingTest {
val heavyWorker = HeavyWorker()
val expected = 666666671666
val result = heavyWorker.heavyOperation()
assertEquals(expected, result)
}-
runBlockingTestwas introduced as a newer coroutine builder thanrunBlocking - But test still fails with exception
java.lang.IllegalStateException: This job has not completed yet - This happened because the test finished before the
SUThas finished execution.
when using kotlinx.coroutines.test.TestCoroutineDispatcher.runBlockingTest --- > This is the best approach available
- This is the same as
runBlockingTestbut an additional add-on here is we provide the dispatcher during the unit test in place of what is being used in the production code. - We shall explain in the section below