It’s a bit of a bummer that we need to insert this yield. We don’t even know if it’s enough to get things to always pass. Maybe if we run this enough times it will eventually fail, leading us to insert a few additional yields to push things along.
One thing that could potentially fix this is if Swift supported async deinit
for objects, because then we could suspend for a bit when the test store is torn down to see if all effects finished, and if not then we could error. There has been some discussions of this in the evolution forums, but no movement on a final design yet.
But, even before we get that feature, there’s still something we can do to make this work deterministically without sprinkling yields into the test. It is possible to send an action to the store and get back a task that represents the lifecycle of the effect that was kicked off from that action. That would give you something concrete to await
on so that you could suspend until the exact moment that the effect finishes. This would give us a 100% deterministic way to make our test pass.
It also turns out that by getting a handle on an effect’s lifecycle we can improve other parts of the library too. For example, SwiftUI has an interesting view modifier called .task
that allows you to spin up some asynchronous work when the view appears, and that work is automatically cancelled when the view disappears.
Wouldn’t it be cool if we could send an action in that task modifier so that when the view disappears it cancels the inflight effect that was kicked off from the action? This makes it possible for a feature to seamlessly tear down its effects when the view disappears.
Let’s see how this is possible.