Skip to content

In 1.6.10 runTest, advanceUntilIdle never ends if collecting a flow with sample operator #3135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
lotdrops opened this issue Jan 12, 2022 · 4 comments
Labels

Comments

@lotdrops
Copy link

When testing flows with the new api runTest I generally collect values of the flow I want to test, advanceUntilIdle, and read the latest value. This way, I ignore intermediate values that are generally not relevant, and stick to the final value.
If I do this with a flow that uses the sample operator the test runs forever, stuck at the advanceUntilIdle() call.
Simplified example:

    private val dispatcher = StandardTestDispatcher()
    @Test
    fun `test ab`() = runTest(dispatcher) {
        val testVar = AbTest()
        val values = mutableListOf<String>()
        val observer: Job = launch {
            testVar.c.collect { values.add(it) }
        }
        advanceUntilIdle()
        assertEquals("ab", values.last())
        observer.cancel()
    }

    class AbTest {
        val a = MutableStateFlow("a")
        val b = flowOf("b")

        val c = combine(a.sample(500),b) { a,b -> a+b }
    }

Also, in other tests that did not complete with runTest, for example due to using a different dispatcher, the test would finish after 1 min with an exception. In the case of sample it does not, it runs forever.

@dkhalanskyjb
Copy link
Collaborator

Works as intended.

The reason is that the coroutine behind c is never idle, it will keep producing values. So, advanceUntilIdle behaves correctly. You can use advanceTimeBy to only run the coroutines for a given period of virtual time, then the test passes.

the test would finish after 1 min with an exception. In the case of sample it does not, it runs forever.

Sure: the test times out after a minute of inactivity, but here, there is the activity of waiting for a to produce a new value.

@lotdrops
Copy link
Author

I understand that due to the current implementation of sample, as it has a ticker that is always running advanceUntilIdle works as intended.
However, from the perspective of a user, if the flow that is being sampled is idle I'd expect advanceUntilIdle to work as described. And there are use cases where that behavior would be very useful, otherwise I do not have a way to test "the latest value that is emitted".
I think we should not need to change how we test a flow to a less robust approach if standard operators like sample are used due to the specific implementation details of such operator.
Can't sample be implemented in a way that it is idle if the upstream flow is idle?

@hfhbd
Copy link
Contributor

hfhbd commented Oct 14, 2022

Is this the same reason why this test also does not work?

@Test
fun c() = runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
  flow { emit(0) }.shareIn(this, started = SharingStarted.Lazily)
}

@dkhalanskyjb
Copy link
Collaborator

±. Both are examples of a coroutine existing but not finishing its work. An even simpler example is just this:

    @Test
    fun c() = runTest(dispatchTimeoutMs = 1000) {
        launch(start = CoroutineStart.LAZY) {
        }
    }

(This reminds me that we should add a runTest overload that accepts a Duration)

This issue is not closed, despite the test framework working as intended because maybe there's a good answer to this question:

Can't sample be implemented in a way that it is idle if the upstream flow is idle?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants