I am trying to test the values emitted by a `Mutab...
# compose
s
I am trying to test the values emitted by a
MutableState
using
snapshotFlow
and turbine. A simplified version of the class looks like like this
Copy code
class Foo(scope: CoroutineScope) {
    val bar = mutableStateOf(1)

    init {
        scope.launch {
            delay(1000)
            bar.value = 2
        }
    }
}
The test method looks like this
Copy code
@Test
fun test() {
    val testScope = TestScope(StandardTestDispatcher())
    testScope.runTest {
        val foo = Foo(testScope)
        snapshotFlow { foo.bar.value }
            .test {
                val item1 = awaitItem()
                println(item1)
                val item2 = awaitItem()
                println(item2)
            }

    }
}
My expectation was that it would print
1
followed by
2
. This test always fails with timeout after printing
1
. When using
snapshotFlow
outside of the test environment it works as expected. I am unclear on what I am doing wrong and was looking for some guidance. Thank you.
j
I expect you need some code like:
Copy code
var scheduled = false
Snapshot.registerGlobalWriteObserver {
  if (!scheduled) {
    scheduled = true
    scope.launch {
      scheduled = false
      Snapshot.sendApplyNotifications()
    }
  }
}
and note calls to
registerGlobalWriteObserver
return a handle that you could call to dispose of your observer. you could create a JUnit rule that added this for all tests in that file, for example
s
Thank you, that works. I will add a rule that adds this to every test. I am still not completely clear on why it works, is there some resource that I can read to help me better understand what I was missing?
j
Maybe this talk? https://www.droidcon.com/2022/09/29/opening-the-shutter-on-snapshots/ I can't remember if it was covered.
s
Will check this out. Thank you.
j
it's an illuminating talk regardless
z
Unfortunately I don’t think I covered that bit in a lot of detail, might mention it at one point though. I don’t think I’ve even covered it in my snapshot blogs but I should! Here’s the docs fwiw.
j
maybe you've done the work but you haven't applied it yet
and i'm just sending you a notification that it's time
z
Might not happen for a bit yet, I won’t see new work until the global year id is advanced
Basically there are two ways for the snapshot system to realize something changed: 1. A snapshot is applied to the global snapshot in which that state object was written to. 2. The state object is written to in the global snapshot. This is the case in your test. When the former happens, it sends an “apply notification”, which is what
snapshotFlow
uses to trigger the flow emission. When the latter happens, no apply notification is sent until the next snapshot is applied (might be never) so Jake’s code manually fires it. Compose UI, Jake’s Mosaic, and Molecule all have code that basically looks exactly like that. The reason these are separate steps is because many different states might get written to at different times, and the runtime might not want to process those updates until some point in the future (eg the next frame).
s
Thank you, that does clear up a lot of my confusion. I will be sure to listen to your talk and try to read up more on this.
a
Late reply but might be useful: this is where that automatic
sendApplyNotifications
is being done by Compose UI: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]androidx/compose/ui/platform/GlobalSnapshotManager.android.kt One notable edge-case of this in production code if you’re using interop is that
snapshotFlow
won’t emit updates unless a piece of Compose UI has been displayed at least once: https://issuetracker.google.com/issues/228228179