SharedFlow that suspends even if there are no subscribers?

The documentation states:

In the absence of subscribers only the most recent replay values are stored and the buffer overflow behavior is never triggered and has no effect. In particular, in the absence of subscribers emitter never suspends despite BufferOverflow.SUSPEND option and BufferOverflow.DROP_LATEST option does not have effect either. Essentially, the behavior in the absence of subscribers is always similar to BufferOverflow.DROP_OLDEST, but the buffer is just of replay size (without any extraBufferCapacity ).

In a unit test, I need to simulate a device’s behavior. Normally, I get input data from it through a SharedFlow. In the test, I want to simulate this with a hardcoded list of simulated incoming data. However, the problem is that if I do that, all that data is emitted immediately, because the SharedFlow doesn’t suspend if there are no subscribers. This means that I can’t do this as part of a test setup:

fun testInit() {
    testScope.launch {
        while (simulatedInputList.hasNext()) {
            simulatedSharedFlow.emit(simulatedInputList.next())
        }
    }
}

This loop will run immediately and go through the entire list because emit will drop all data.

What I want instead is for emit to suspend until a subscriber consumes data.

Is there a way to do this?

I believe this is impossible, because hot flows by design don’t suspend while emitting to them. And it works exactly the same in the real code that you try to simulate, so I assume the problem is not really with SharedFlow, but that your simulation is not an exact equivalent of your real case.

If you want to emit with some delays, then use delay() between emits. If you want to collect all items that you emitted, no matter the timing, then either start collecting first and only then start emitting or use a cold flow and convert it to SharedFlow using shareIn() and SharingStarted.Lazily. If you have a lot of items to emit, then for testing purposes create a big replay buffer.

edit:
And if you don’t control how this SharedFlow is created then the only solution I see is to intercept collections and emit according to collections. But maybe there is a better way.

1 Like

If you need to mock/fake a Flow and have a list of values why not just use List.asFlow()?

If you need it to be a SharedFlow (I doubt it but maybe), SharedFlow.onSubscription or listening to MutableSharedFlow.subscriptionCount could help.

Hmm I see. Yeah, I think I am better off rethinking the whole thing. Might require a more elaborate mockup, but this seems to me as if I’m fighting fundamental design decisions, which would get me nowhere.