cashapp / cashapp/molecule

Compose stops recomposing in tests

Open
#326 8 comments 0 reactions 0 assignees View on GitHub
Dominant language
Kotlin
Stars
2.2k
Forks
116
Avg merge
1h 36m
Merged PRs (30d)
8

Description

In a weird combination of `StateFlow`, `backgroundScope`, `UnconfinedTestDispatcher`, `collectAsState()` and the `Immediate` recomposition mode the Compose runtime stops recomposing and Flows produced by Molecule stop sending events.

This test passes:
```
@Test
fun `something breaks`() = runTest {
val _strings = MutableStateFlow("one")
val strings: Flow = _strings

val molecule = (backgroundScope + UnconfinedTestDispatcher(testScheduler)).launchMolecule(RecompositionMode.Immediate) {
val string by strings.collectAsState("one")
string
}

molecule.test {
assertThat(awaitItem()).isEqualTo("one")

_strings.value = "two"
assertThat(awaitItem()).isEqualTo("two")

_strings.value = "three"
assertThat(awaitItem()).isEqualTo("three")
}
}
```

If I change the initial value to something else such as `collectAsState("abc")`, then only "one" from the StateFlow is emitted but no other value anymore even though the StateFlow value changes. The composable function only recomposes when the initial value of the `StateFlow` and the `collectAsState` function are equal. This seems to be the bug.

Changing the flow to a `MutableSharedFlow` fixes the issue, because there’s no default value. Removing the `UnconfinedTestDispatcher` also fixes the issue. In this case it emits the initial value from `collectAsState` first, then the default value from the `StateFlow` and then all other changes. Using the same initial values also resolves the bug (the problem here is that `StateFlow` is an implementation detail of the underlying API, this example is simplified). It only breaks when the initial values are different.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by running the provided runTest reproduction with StateFlow, backgroundScope, UnconfinedTestDispatcher, launchMolecule, RecompositionMode.Immediate, and collectAsState. Compare the differing and matching initial values, then trace why recomposition stops; done means later StateFlow changes emit consistently in the failing case.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.