Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CoroutineScope.launch doesn't work in TestScope

I have a function that collects a flow and does other operations through launching other coroutines, in my tests for that function it looks like none of my coroutines launches. is there something I am missing here?

class Tax(
    private val coroutineScope: CoroutineScope,
) {
    fun operate() {
        coroutineScope.launch {
            // some work done here 
        }
    }
}

// My Test class 
@Test
fun `test operate`() = runTest {
    val tax = Tax(this)
    tax.operate()
    // verify something with in the coroutineScope.launch always fails
}
like image 943
Abdelrhman Talat Avatar asked Apr 10 '26 00:04

Abdelrhman Talat


1 Answers

You either need to wait till testScheduler schedules coroutine by calling advanceUntilIdle, calling coroutineScope or use UnconfinedTestDispatcher as coroutineContext with new TestScope as

class SampleTest {
    private val coroutineScope = TestScope()

    // My Test class 
    @Test
    fun `test operate`() = coroutineScope.runTest {
       val tax = Tax(coroutineScope)
       
       tax.operate()
       advanceUntilIdle()

       // verify something with in the coroutineScope.launch always fails
    }

}

or use UnconfinedTestDispatcher as

class SampleTest {
    private val coroutineScope = TestScope(UnconfinedTestDispatcher())

    // My Test class 
    @Test
    fun `test operate`() = coroutineScope.runTest {
        val tax = Tax(coroutineScope)

        tax.operate()
        advanceUntilIdle()

        // verify something with in the coroutineScope.launch always fails
    }
}

You can check out these samples i did based Android Coroutine Test document

class UserRepository {
    private val users = mutableListOf<String>()

    fun registerUsers(user: String) {
        users.add(user)
    }

    suspend fun registerUserAsync(user: String) {
        delay(100)
        users.add(user)
    }

    fun getAllUsers(): MutableList<String> {
        return users
    }
}

And tests

join

@Test
fun standardTestWithJoin() = runTest {
    val userRepo = UserRepository()

    val job1 = launch { userRepo.registerUsers("Alice") }
    val job2 = launch { userRepo.registerUsers("Bob") }

    job1.join()
    job2.join()
    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
}

advanceTillIdle

 // 🔥 This test passes with registerUserAsync too
    @Test
    fun standardTest2() = runTest {
        val userRepo = UserRepository()

        launch { userRepo.registerUsers("Alice") }
        launch { userRepo.registerUsers("Bob") }
        advanceUntilIdle() // Yields to perform the registrations

        assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
    }

UnconfinedTestDispatcher

// 🔥 This test fails with registerUserAsync(delay)
@Test
fun unconfinedTest() = runTest(UnconfinedTestDispatcher()) {
    val userRepo = UserRepository()

    launch { userRepo.registerUsers("Alice") }
    launch { userRepo.registerUsers("Bob") }

    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
}

@Test
fun unconfinedTest2() = runTest(UnconfinedTestDispatcher()) {
    val userRepo = UserRepository()

    launch { userRepo.registerUserAsync("Alice") }
    launch { userRepo.registerUserAsync("Bob") }
    // 🔥 Need to call for past to test
    advanceUntilIdle()

    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
}

https://developer.android.com/kotlin/coroutines/test#standardtestdispatcher

like image 75
Thracian Avatar answered Apr 11 '26 13:04

Thracian