Approach for testing Swift Concurrency when task is initiated by synchronous code

This question is somewhat related to Reliably testing code that adopts Swift Concurrency? - #67 by stephencelis, but enough of a variation to warrant it's own thread.

I'm trying to determine the most elegant approach to awaiting an asynchronous bit of code to execute, to verify that it is indeed triggered.

For example, we have a ViewModel with a few properties that should trigger a network call on didSet, which is a synchronous context. We are ultimately using a Task to call an async service method (which is mocked, for the purposes of the test).

The mocked service uses an expectation to verify that it is called, but this doesn't reliably get called if I don't await the task somehow.

I've taken to exposing the Task? property on the ViewModel so that I can await it immediately after setting one of the properties that will initiate the didSet in the unit test. This feels clumsy to me.

In another scenario, I actually @Published the Task? property so I could use .values to create an AsyncPublisher and use a for await loop to wait for a non-nil Task so the test was a little bit more bulletproof against timing issues. That won't work for @MainActor objects where I want to clean up the task in deinit, because marking the property as @Published triggers a compiler error about mutating a actor property in a non-isolated thread when I do deinit { task?.cancel() }. I'm not sure why. I'm not sure if it even should be working without being published: Deinit and MainActor - #26 by bobspryn

1 Like