Assert for effects in flight

I've been working a bit more with running effects on background threads and was writing some tests for this behaviour. But a lot of these background run effects are of the .fireAndForget variety.
This means that at the end of my tests I have to run something like mainScheduler.advance() for the test to pass since there's still some of those in flight. I'd like to make this more precise in my test by intermittently checking for inflight effects. In TestStore's private completed() method there's a check for longLivingEffects and if there still are any it fails. Could we maybe expose this behaviour to make tests more precise?
For example (Using a test from my AVFoundation post earlier today)

    store.assert(
        .send(.onAppear), // returns a concatenated effect The first one is received below the second one is fire and forget
        .receive(
            .cameraClient(.providePreviewLayer(caLayer)),
            { $0.previewLayer = caLayer }
        ),
        .do { backgroundQueue.advance() },
        .do { mainQueue.advance() },        // schedulers catch the in flight effect here
        .send(.takePictureButtonTapped), // also sends a concatenated effect
        .receive(
            .cameraClient(.pictureTaken(Data())),
            {
                $0.picData = Data()
                $0.isTaken = true
            }
        ),
        .send(
            .retakePictureButtonTapped,
            { $0.isTaken = false }
        ),
        .do { backgroundQueue.advance() },
        .do { mainQueue.advance() }
    )

It would be nice to add an assertion that no more effects are in flight after receiving the .providePreviewLayer action since if I hadn't added the schedulers it would be unclear from the test if there would still be effects in flight.
First thoughts are a Step static called .assertNoActionsInFlight, but there are probably cleaner solutions.
Would like to hear if this is considered useful and any thoughts on implementing. Once there's some consensus on an API I'd love to make a PR.
Cheers!

Technically this is already supported with the store.assert method. All you have to do is close the .assert at the point you want to say that all received actions and effects have been handled, and then start a new store.assert with the rest of the actions:

store.assert(
  .send(.onAppear),
  ...
) // this will pass only if all actions have been received and all effects have completed

store.assert(
  .send(.takePictureButtonTapped),
  ...
)

One wrinkle is that we have a new way of doing store assertions that flattens things a bit. However, with that new style you cannot easily accomplish what you want because the store asserts that actions have been received and effects completed when the store deinits.

However, there is a method that does this work that is currently private and so we could make it public. We just need to find a good name for it. We'll think about it, and let us know if you have any suggestions!

How about assertCompleted()?

maybe assertEffectsCompleted() in that case? Or to be more precise assertAllLongLivingEffectsCompleted() which may be a bit lengthy...

@mbrandonw Ah I hadn't thought about running multiple asserts thanks. One question completed() is still called at the end of every assert though right? Or are you saying you're planning to remove that behaviour?

Maybe we could make it even more precise by also making it possible to assert on the amount of effects in flight? Then we can send an action, assert on there being two effects in flight. receiving one, asserting on one effect being in flight, advance our scheduler and assert in 0 tasks being in flight. To prevent that tests are waiting/advancing schedulers for effects that are actually no longer emitted.
Let me know your thoughts!
This may also generalize nicely to the amount of actions left to receive? But maybe that makes less sense since you have to actively receive those before doing anything else anyway.

Yes, completed is called at the end of .assert and always will be. It's also called when the TestStore is deallocated.

1 Like
Terms of Service

Privacy Policy

Cookie Policy