eygraber
10/20/2024, 5:02 AMdelay(1)
when causing state changes within a LaunchedEffect
so that molecule doesn't skip any state changes:
runTest {
moleculeFlow(RecompositionMode.Immediate) {
val state = model.currentState()
LaunchedEffect(Unit) {
model.validate("")
delay(1)
state.text.edit { append("A") }
}
state
}.test {
awaitItem().validationResult shouldBe NameValidator.Result.Valid
awaitItem().validationResult shouldBe NameValidator.Result.Required // without delay(1) this is missed
awaitItem().validationResult shouldBe NameValidator.Result.Valid
cancel()
}
}
My setup doesn't seem to be the most typical to begin with, but is there something I could change to not need the delay(1)
?saket
10/20/2024, 5:22 AMeygraber
10/20/2024, 5:32 AMmodel.currentState()
is backed by a Compose MutableState
, and I don't think they do anything like that.saket
10/20/2024, 5:38 AMsaket
10/20/2024, 5:38 AMmoleculeFlow
which returns a Flow
and not a StateFlow
kevin.cianfarini
10/20/2024, 12:52 PMmoleculeFlow
doesn't mean that the compose state isn't switching values so quickly that it doesn't have time to recompose for an intermediary state, and thus produce a new emissionkevin.cianfarini
10/20/2024, 12:52 PMdelay
here allows other coroutines to run, which in this case is a recomposition that then emits your valuekevin.cianfarini
10/20/2024, 12:55 PMdelay
is standing in for a network call, it would be better to not allow that function to return until your SUT is ready. We do this by queuing elements into our fake network service only after the test is otherwise ready for the network call to completeeygraber
10/20/2024, 1:49 PMeygraber
10/20/2024, 1:52 PMyield
doesn't work though, which I found weird.eygraber
10/20/2024, 1:59 PMmodel
basically looks like
class Model(val validator: NameValidator) {
private var validationResult = mutableStateOf(NameValidator.Result.Valid)
@Composable
fun currentState() = MyState(
text = rememberTextFieldState(),
validationResult = validationResult,
)
fun validate(text: CharSequence): Boolean {
validationResult = validator.validate(text)
return validationResult.isValid
}
}
kevin.cianfarini
10/20/2024, 3:38 PMeygraber
10/20/2024, 3:53 PMeygraber
10/20/2024, 6:11 PMTextFieldState
?kevin.cianfarini
10/20/2024, 8:27 PMrunTest {
val trigger = Channel<Unit>()
moleculeFlow(RecompositionMode.Immediate) {
val state = model.currentState()
LaunchedEffect(Unit) {
model.validate("")
trigger.recieve()
state.text.edit { append("A") }
}
state
}.test {
awaitItem().validationResult shouldBe NameValidator.Result.Valid
trigger.send(Unit)
awaitItem().validationResult shouldBe NameValidator.Result.Required
awaitItem().validationResult shouldBe NameValidator.Result.Valid
cancel()
}
}
This channel coordinates your SUT and your testing code.eygraber
10/20/2024, 8:46 PMkevin.cianfarini
10/21/2024, 11:27 AMeygraber
10/21/2024, 8:16 PMkevin.cianfarini
10/21/2024, 8:16 PMkevin.cianfarini
10/21/2024, 8:18 PMclass MyFakeNetworkService : NetworkService {
private val channel = Channel<Foo>(Channel.Buffered)
suspend fun getFoo(): Foo = channel.recieve()
fun enqueueFoo(foo: Foo) = channel.trySend(foo).getOrThrow()
}