0

We use the Pointfree Combine Schedulers, specifically the TestScheduler, extensively in our unit tests.

Is there a way to use this same library, or a similar test scheduler library, with the new async/await in Swift? Basically, can you programmatically move and advance through time in the reactive streams produced by async/await?

Why might we want this? As Pointfree describes:

This scheduler is useful for testing how the flow of time effects publishers that use asynchronous operators, such as debounce, throttle, delay, timeout, receive(on:), subscribe(on:) and more

It appears that Swift has recently introduced Swift Async Algorithms that include support for many of these operators. How does the community test these operators?

A similar question was asked a few months back, but the solution seems to propose waiting for the actual amount of time. Obviously if you have a 10 or 30 second timeout, one does not want to literally wait 10 or 30 seconds in their test.

esilver
  • 27,713
  • 23
  • 122
  • 168
  • 1
    Why would you need to? You just make a `async` test method and presto, you're walking through the async calls one by one. That's kind of the point of `async/await`, isn't it? You don't need to "wait" for anything any more, because `async/await` itself is a waiting mechanism. – matt Dec 20 '22 at 18:02
  • How do you test timeouts? An async call that you only want to wait for up to 10 seconds lets say, and then emit an error? – esilver Dec 20 '22 at 18:29
  • 2
    Async/Await doesn't produce reactive streams. You can create an AsyncSequence, but stepping through it is "locally synchronous". You can just step through the task that is looping over the sequence. How do you propose to write a task that awaits for something with a timeout? Can you add sample code to the question? – Scott Thompson Dec 20 '22 at 19:11
  • There's a `async` version of `wait` that includes a timeout. Once you are `async/await` you can be all `async/await` all the time. – matt Dec 20 '22 at 20:33
  • I have updated my question. How do people test operators like debounce, throttle, delay, and timeout using `AsyncSequence`? – esilver Dec 22 '22 at 20:42
  • You DO need test schedulers. They are primarily needed for unit tests; especially to ensure that there are no pending tasks. Through this you can be sure that an inverted expectation never gets fulfilled. Check out [Kotlin's TestCoroutineScheduler](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scheduler/#-1985540204%2FFunctions%2F1391162071) – funct7 Aug 20 '23 at 08:04

3 Answers3

1

The authors of https://github.com/pointfreeco/combine-schedulers have released https://github.com/pointfreeco/swift-clocks, which serves the same purpose for AsyncSequence.

Petr Pavlík
  • 178
  • 1
  • 10
0

If I understand your question correctly, you are looking into a way to test async/await code. If that's correct, let's image you have the following DataDownloader which will execute an async function to download something:

struct DataDownloader {
    func downloadData() async throws -> Data {
        ...
    }
}

In order to call that function we will need to use await, which would basically mark that the calls need to happen in a concurrency supported context. Fortunately Apple's XCTest has been upgraded to support that. We don't need to wrap it in a Task. So the trick is that in your test you can mark any function as async and you will be use the await. A simple example below how would you call the Downloader async function in a test context:

class DataDownloaderTests: XCTestCase {
    func testDataDownload() async throws {
        let dataDownloader = DataDownloader()
        let downloadedData = try await dataDownloader.downloadData()

        XCTAssertNil(resizedImage.size)
    }
}
πter
  • 1,899
  • 2
  • 9
  • 23
  • Right, well, that's what I already said, basically. – matt Dec 20 '22 at 20:35
  • Sorry, missed your comment. Yes, exactly as you suggested ! – πter Dec 20 '22 at 20:47
  • Thank you for this example. Let's say you are trying to test that, if no download completes in 10 seconds, it throws an exception and the stream terminates. How does one write and test this code using async/await? – esilver Dec 21 '22 at 20:07
  • Well, the test will wait until the await code finishes. So the nice thing about using async/await in tests that you don't need to explicitly use the XCTestExpectation – πter Dec 22 '22 at 09:39
  • So does this mean the test will stall for 10 seconds until the timeout is reached? – esilver Dec 22 '22 at 16:16
  • It will stall until it get's some return. The responsibility is on the `async await` function to always return something – πter Dec 26 '22 at 09:22
-1
func AsyncFunction() async -> Int {
  let result = await AsyncOperation()
  return result
}
let scheduler = TestScheduler()
let result = try scheduler.start {
  return try AsyncFunction().wait()
}
XCTAssertEqual(result, 42)