Skip to content

What is the preferred way of testing when the code being tested launches a never-ending coroutine? #3283

@azabost

Description

@azabost

What is the preferred way of unit testing classes which internally launch a never-ending coroutine such as SharedFlow collection?

Let’s say:

class MyClass(coroutineScope: CoroutineScope, flowToCollect: Flow<Int>) {
    
    var lastObservedResult: Int? = null

    init {
        coroutineScope.launch {
            flowToCollect.collect { lastObservedResult = it }
        }
    }
}

If I use runTest and pass the created TestScope then the test is going to fail after some time because there is a running coroutine.

@Test
fun testMyClass() = runTest {
    MyClass(this, flow)
    // do something here, make some assertions etc.
    // at the end, the test is going to fail because of the running coroutine
}
After waiting for 60000 ms, the test coroutine is not completing, there were active child jobs (...)

So should I create another scope instead? Like this, for example?

val dispatcher = UnconfinedTestDispatcher()

@Test
fun testMyClass() = runTest(dispatcher) {
    val additionalScope = CoroutineScope(dispatcher)

    MyClass(additionalScope, flow)
    // do something here, make some assertions etc.

    additionalScope.close() // Is this necessary, btw? Is the launched coroutine going to leak after the test is finished or something?
}

I'm trying to figure out what are the best practices in such cases.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions