Question around using `snapshotFlow` for unit test...
# compose
t
Question around using
snapshotFlow
for unit testing state classes 🧵 ā¬‡ļø
I have a state class that I would like to test in isolation (i.e. not a device UI test):
Copy code
class FooState {
    val buttonIsVisible = mutableStateOf(false)
    val buttonText = mutableStateOf("")
    fun update() = { /** internally updates states **/ }    
}
I know that we can use
snapshotFlow
for unit testing the
update
and the resultant changes to the `MutableState`s. But I would really like to take advantage of the by delegate and declare those `MutableState`s as
vars
. Is there a way to ā€œobserveā€ the state values in a unit test if by delegates were used here? Not sure if I am missing something obvious…
a
Have you tried to to use delegates yet? You might be pleasantly surprised!
ā˜ļø 1
šŸ™ˆ 1
y
This series explains how it works and why state fields work even down hidden through the call stack https://dev.to/zachklipp/introduction-to-the-compose-snapshot-system-19cn
šŸ™šŸ¼ 1
t
ack, had to stop working on this a bit…got back to it now realized two things: a) i was still in the mindset of how we model states using
StateFlow
, i.e. one can update that multiple times, while `toList()`ing the incoming updates in a separate coroutine. then, finally asserting that the list contained all the expected updates. with
mutable/derivedStateOf
+ delegation, one could just run the function under test and assert the very next update (instead of asserting a slew of updates as with a flow/snapshotFlow). b) there was a bug in my test šŸ˜– that I had to fix, after which using regular by delegation worked! šŸ™šŸ¼ thank you! For completeness, heres parts of the state + test:
Copy code
class CounterState(
    initialCount: Int,
    maxCount: Int
) {
    private var counter by mutableStateOf(initialCount)
    val isAtMax by derivedStateOf { counter == maxCount }
    fun increment() { counter++ }
}
Copy code
@Test // This passes! 
fun `isAtMax - given incremented up to specified max, should return true`() {
    val state = CounterState(initialCount = 0, maxCount = 1)
    state.increment()
    assertThat(state.isAtMax).isTrue()
}
a
i was still in the mindset of how we model states usingĀ 
StateFlow
, i.e. one can update that multiple times, whileĀ `toList()`ing the incoming updates in a separate coroutine. then, finally asserting that the list contained all the expected updates.
That mindset it still correct for Compose’s state system, with the difference being in how you observe changes. You should still prefer to use
snapshotFlow
in tests, so that you are also checking that your state changes will be observed properly by Compose. In other words, right now you are testing that the value is correct when you query for it, not that a composable would be updated properly whenever it changes. Concretely, that test wouldn’t be able to distinguish between these two implementations of `CounterState`:
Copy code
class CounterState(
    initialCount: Int,
    private val maxCount: Int
) {
    private var counter by mutableStateOf(initialCount)
    val isAtMax get() = counter == maxCount
    fun increment() { counter++ }
}
Copy code
class CounterState(
    initialCount: Int,
    private val maxCount: Int
) {
    private var counter = initialCount
    val isAtMax get() = counter == maxCount
    fun increment() { counter++ }
}
The first one will function equivalently to
derivedStateOf
(and both of those have correct behavior), but the test would also pass for the second version. Because the second one doesn’t use
mutableStateOf
, a composable that reads
isAtMax
won’t be recomposed whenever
counter
changes.
šŸ‘šŸ» 1
t
Concretely, that test wouldn’t be able to distinguish between these two implementations ofĀ `CounterState`:
oh doh! yes indeed šŸ™šŸ¼ makes sense, will make sure to use
snapshotFlow
238 Views