I’m working on a class that handles performing a s...
# test
p
I’m working on a class that handles performing a suspending task (logging the user in) as well as maintaining a SharedFlow so other components can be alerted when the task is performed:
Copy code
class ExampleClass(
    private val loginApi: LoginApi,
    externalScope: CoroutineScope,
) {
    private val _loginFlow = MutableSharedFlow<String>()

    val loginFlow = _loginFlow.shareIn(externalScope, SharingStarted.Lazily)

    suspend fun performLogin(): String {
        val result = loginApi.performLogin()
        _loginFlow.emit(result)
        return result
    }
}

interface LoginApi {
    suspend fun performLogin(): String
}
I’ve written a test case for this:
Copy code
class ExampleClassTest {

    private val mockApi = mockk<LoginApi> {
        coEvery { performLogin() } returns "hello"
    }

    private val testCoroutineScheduler = UnconfinedTestDispatcher()
    private val testScope = TestScope(testCoroutineScheduler)

    @Test
    fun `this test passes`() = runTest(testCoroutineScheduler) {
        val exampleClass = ExampleClass(mockApi, testScope)
        expect("hello") {
            exampleClass.performLogin()
        }
    }
}
Can anyone help me understand why calling runTest directly on my TestScope causes this test case to fail with an eventual UncompletedCoroutinesError? The error message says the test coroutine is not completing, and also that there’s an active child job.
Copy code
@Test
    fun `this test times out`() = testScope.runTest {
        val exampleClass = ExampleClass(mockApi, testScope)
        expect("hello") {
            exampleClass.performLogin()
        }
    }
A couple things I’ve noticed: StandardTestDispatcher vs TestCoroutineScheduler vs UnconfinedTestDispatcher doesn’t matter in this case