Using Turbine, I’ve got a fake impl of something l...
# squarelibraries
s
Using Turbine, I’ve got a fake impl of something like this (simplified)
Copy code
class FakeFoo : Foo {
  val responseTurbine = Turbine<Bar>()
  override suspend fun invoke(): Bar {
    return responseTurbine.awaitItem()
  }
}
Then in a test, I am testing the result of a StateFlow, which comes from a result of combining two things, something like this
Copy code
val data: StateFlow<State> = combine(someOtherFlow, flow { emit(FakeFoo().invoke()) }.onStart{ emit(null) }) { someOtherFlowValue, bar -> State(someOtherFlowValue, bar) }
But in one particular test I am not interested in the result of FakeFoo, so I want to rely on just the initial value emitted from the
onStart
(Is a StateFlow with a default value in my case) to only test the other half of the
combine
, whatever comes from
someOtherFlow
. However, since this
Turbine<Bar>
doesn’t ever get a value, but I am awaiting on it, it crashes my test with “no value received in X seconds”. Doing
cancelAndIgnoreRemainingEvents()
isn’t an option either, since now instead of infinitely waiting on
awaitItem()
it will instead crash with “Expected item but found Error”. Is there some other approach I can take instead? Maybe I shouldn’t be using a Turbine for my fake impl? I’ve taken inspiration to go with this approach from

this talk

and it has worked flawlessly in all the other cases I’ve tried.
I can provide some default value in my test, so that I can make this work, and this is what I’ve done to make this work for now, but it’s bloating up the test with unrelated things that are unrelated to what I am testing in that particular test. So since I am providing values to the fake impls using Turbine for both the two flows in the
combine
call, in the test I now need to pass values to both, even though I only wanna test one of them in some of the tests which are meant to be simpler.
Or I mean I can also do
Copy code
private class FakeFoo(
  block: Turbine<Bar>.() -> Unit = { add(SomeSaneDefault) },
) : Foo {
  val responseTurbine = Turbine<Bar>().apply(block)
  override suspend fun invoke(): Bar {
    return responseTurbine.awaitItem()
  }
}
So that from the test perspective, it can just be calling
FakeFoo()
and at least visually it will not draw more attention than it needs to draw. With the downside that someone could accidentally rely on this default behavior, and make that test much less intuitive to read and understand 😅
b
You could pass a null timeout to Turbine for this test only?
s
Tried that, but
null
timeout is the default parameter though, and that seems to default to 3 seconds somehow. https://github.com/cashapp/turbine/blob/c7c3a68a32edfcd7c65102e630647b4808f8d058/src/commonMain/kotlin/app/cash/turbine/Turbine.kt#L98
b
The comment explains it
timeout If non-null, overrides the current Turbine timeout for this [Turbine]
I would just send a fake response you could ignore then
s
Yeah, if it exists it overrides, otherwise it seems to always be 3s. I suppose there’s no way to say “I don’t want this to timeout”
I would just send a fake response you could ignore then
Yeah I guess I gotta do that. Just makes the test bloated with unrelated stuff, which is what I wanted to avoid if at all possible.
b
If you are on the latest version of Coroutines, you can just add a
timeout
operator to the flow of something like a millisecond. It'll throw a cancellation exception on the upstream flow, but allows you to catch it downstream, so your coroutine isn't cancelled.
s
Yeah but then my test would need to have extra code to catch the exception, which would be even less optimal for the unrelated noise to what I actually want to test. Also, that flow in particular is then as I mentioned before, `combine()`d into the final state, so it’s propagate to that too. So I don’t think I can do that here, at least not if I don’t actually catch this exception in the production code, and not inside the test itself.