Getting kotlin error "After waiting for 60000 ms, the test coroutine is not completing"
Asked Answered
M

9

23

I'm new at testing, trying to take second flow value and assert it, When i run this test one by one runs fine but when i run whole test once first test runs fine and rest of test give me timeout error.

Error :

After waiting for 60000 ms, the test coroutine is not completing
kotlinx.coroutines.test.UncompletedCoroutinesError: After waiting for 60000 ms, the test coroutine is not completing
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$3$3.invokeSuspend(TestBuilders.kt:304)
    (Coroutine boundary)
@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {

    private lateinit var viewModel: HomeViewModel
    private val testDispatcher = UnconfinedTestDispatcher()

    @Before
    fun setup() {
        viewModel = HomeViewModel(FakeOrderRepository())
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
        testDispatcher.cancel()
    }

    @Test
    fun flowViewModelTesting1() = runTest {
        val result = viewModel.homeUiState.drop(1).first()
        assertThat(true).isTrue()
    }


    @Test
    fun flowViewModelTesting2() = runTest {
        val result = viewModel.homeUiState.drop(1).first()
        assertThat(true).isTrue()
    }
}
Meridethmeridian answered 5/6, 2022 at 10:5 Comment(2)
How do you update the value of homeUiState? Are you sure it is updated?Convent
It looks like homeUiState is always updated only once per test session, so only the first test finishes. Do you share some state/objects between instances of HomeViewModel that might cause homeUiState to be updated only once, even when multiple HomeViewModel instances are created?Convent
P
10

I had the same issue. Replacing UnconfinedTestDispatcher() with StandardTestDispatcher() will solve the problem.

Psilocybin answered 6/8, 2022 at 19:37 Comment(1)
While I agree that StandardTestDispatcher is often better than UnconfinedTestDispatcher, this is not a "one-size fits all" solution and I don't find it very helpful if you're not explaining or linking to the difference between the two.Trowel
Z
3
@RunWith(AndroidJUnit4::class)
class Test {

    private val dispatcher = TestCoroutineDispatcher()
    private val testScope = TestCoroutineScope(dispatcher)
    
    @Before
    fun setUp() {
        Dispatchers.setMain(dispatcher)
    }
    @After
    fun tearDown() {
        Dispatchers.resetMain()
        dispatcher.cleanupTestCoroutines()
        unmockkAll()
    }
    
    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun test() = runTest {
        testScope.launch {
                 // call your suspend method here
                Utils.testSuspendMethod()
            )
          //add here assertions for the method
        }

    }

}
Zakaria answered 28/2, 2023 at 10:27 Comment(0)
P
1

In my case, I just was required to mockk parameters that were required in the functions I called during the flow.

Function1()->Function2()//this Function2 needs mocked variables.
every { mockBanner.bannerKey } returns “mockAnswer”

Here I had Forgot to mock bannerKey in Function2,that resolved this issue.

Painstaking answered 18/12, 2023 at 13:55 Comment(0)
P
1

If you are using kotlinx-coroutines-test-jvm:1.7 or higher you can pass the timeout as an argument. The default argument is 10 seconds.

/**
 * The default timeout to use when running a test.
 */
internal val DEFAULT_TIMEOUT = 10.seconds

...
public fun runTest(
    context: CoroutineContext = EmptyCoroutineContext,
    timeout: Duration = DEFAULT_TIMEOUT,
    testBody: suspend TestScope.() -> Unit
): TestResult 

So you can add the argument to runTest

fun `test load`() = runTest(timeout = 60.seconds) {
...
}
Pudding answered 22/4, 2024 at 19:17 Comment(0)
D
0

Most of the time reason for this error is that you have not finish your observer variable call.

At the end of your test case call finish() method on your observer variable.

observerVariable.finish()
Dorian answered 3/8, 2022 at 2:22 Comment(2)
What is the type of observerVariable in this case?Borderline
This is the hot variable which you are observing. for example Flow.Dorian
B
0

This is maybe because there is a coroutine that never ends in your view model or in his dependencies (like the repository). There is a couple of things you can try:

  1. I am not sure if coroutines debugger is available on Android Studio right now, but if it is you can put a breakpoint after all your test functions and use it to check which coroutine is still running.

  2. Check in your view model and its dependencies if you are using an external scope like CoroutineScope(SupervisorJob() + Dispatchers.Default). If you are, make sure you are using with a suspend function that finishes in 60s. If you are using to observe a value like in myFlow.onEach{}.launchIn(externalScope) it may be that because the job is still running.

  3. Don't replace the dispatchers of your external scope with the test scope (like in CoroutineScope(SupervisorJob() + myTestDispatcher)), otherwise the test execution will block the execution of your suspend functions and the suspend function will only return after your test is over, which may cause this infinite testing behavior or a screen being stucked if you are making instrumented tests. Also, put your scopes and dispatchers on the constructor, so in tests you can control which one is being used.

    @Test
    fun flowViewModelTesting1() = runTest {

        /** Given **/
        val viewModel = HomeViewModel(
            orderRepository = FakeOrderRepository(
                // ioDispatcher may execute execute the task of call the server
                // or in the fake version wait 1,000 ms and return a mock
                ioDispatcher =  TestDispatcher(this.testScheduler),
                // External scope may launch a order update
                externalScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
            )
        )

        /** When **/
        val result = viewModel.homeUiState.drop(1).first()

        /** Then **/
        assertEquals(
            expected = true,
            actual = result.myBoolean
        )
    }
Burstone answered 10/3, 2023 at 18:9 Comment(0)
E
-2

You're not using the testDispatcher. You should pass it to runTest like:

runTest(UnconfinedTestDispatcher()) {}

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

Edge answered 1/11, 2022 at 20:55 Comment(0)
I
-3

When you use runTest, so you're using StandardTestDispatcher by default that means it won't immediately run. You have to launch your test from the same dispatcher that you have been using for a ViewModel

Replace

runTest {

with

testDispather.runTest {

it should work, give it a try. I can't share much without checking your viewmodel code.

Imagism answered 13/7, 2022 at 15:28 Comment(0)
S
-3

All of these answers are wrong, you are looking for dispatchTimeoutMs:

@Test
fun `my long-running test`() = runTest(
  dispatchTimeoutMs = 60000L, // Increase this number as needed, this is the default which is 60 seconds in milliseconds
  context = testDispatcher // also, still pass in a TestDispatcher
) {
  // ... Rest of your long-running test
}

Normally, you should try to make your test run faster, or test smaller things, but I have needed to do this from time-to-time.

Spondaic answered 13/2, 2023 at 6:13 Comment(1)
Increasing this number won't help when a coroutine is actually stuck. While no of the other answers is a solution to all potential variants of this problem, some of them are at least helpful in some situations.Trowel

© 2022 - 2025 — McMap. All rights reserved.