Modern Concurrency: Beyond the Basics

Oct 20 2022 · Swift 5.5, iOS 15, Xcode 13.4

Part 1: AsyncStream & Continuations

09. Unit Testing Tools

Episode complete

Play next episode

About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 08. Wrapping Callback With Continuation Next episode: 10. Conclusion

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Heads up... You've reached locked video content where the transcript will be shown as obfuscated text.

In episode six, when you wrote unit tests, there were two issues, a wait doesn't time out, and the tests take more than five seconds to run because of the one second waits built into the countdown. In this episode, you'll create unit testing tools to fix these issues. You'll create a timeout mechanism, and you'll speed up execution with a custom sleep function. Refresh your browser to make sure the core server is running or restart the server in terminal. Continue with your project from the previous episode or open the starter project for this episode. Build and run the app. This is just to make sure you don't have to wait for a simulator to start up when you run your tests. You can't let your tests hang indefinitely, so you'll create a new type called timeout task. It's like task except it throws an error if the asynchronous code doesn't complete in time. In the blabber test utility group, create a new Swift file called timeouttask.swift. Check its target membership to make sure it only belongs to blabber tests, not to blabber. Now create a class. Like task, timeout task returns a success type. If the task doesn't return a result, success is void. Extend timeout task with an error to throw if the task times out. Add its description. Up in the timeout task class add a property. This is the maximum duration you'll allow for the task. One more property. This is an async throwing closure that conforms to the sendable protocol. As sendable closure is thread safe, it's safe to transfer it between concurrency domains. You'll learn more about sendable when you learn about actors in part two of this course. X-code wants an initializer to start writing one. You accept the maximum duration in seconds, you'll convert this value to nanoseconds to store in the nanoseconds property. Now the second parameter. At escaping means you may store and execute the closure outside of the initializers scope. And in the body, one second is a billion nanoseconds. Now add another property to timeout task, a checked continuation to let you suspend and resume execution. Continuation is an optional so after you use it, you'll be able to destroy it by setting it to nil. And the value property to start the work and asynchronously return the result of the task. You declare its getter as async and throws so you can asynchronously control the timing of the execution for your tests. In the getter, create a checked throwing continuation. This lets you either complete successfully or throw an error if the operation times out. Inside the continuation closure, add a task to sleep for the maximum duration. If the sleep task completes, use the continuation to throw a timeout error. Then destroy the continuation. This takes care of the part of the code that times out. Now add the actual task to perform the operation. Execute the operation passed into the initializer, return the result, and destroy the continuation. You're starting two asynchronous tasks in parallel. Whichever task completes first gets to use the continuation while the slower task gets canceled. It's just possible that both tasks might try to use continuation at precisely the same time leading to a crash. You'll learn about writing safe concurrent code in part two. For now, leave this timeout task code as it is. For completeness, add one more method to timeout task. You make sure the continuation finishes no matter what happens. So, how to use your new timeout task in your unit test. In blabber tests, look at test model countdown. This code could hang at prefix four if it doesn't receive four requests. Wrap this code in a timeout. Task test URL protocol.requests is the operation closure of timeout task. Value starts this task and returns its result. Task.value waits for the task to complete then returns its value similar to a promise in other languages. Now that it's inside the timeout task closure, you need to await test URL protocol requests except X-codes fix. Now check your simulator setting and run the test. Success, just like you got in episode six. So timeout task didn't break anything. You're probably itching to see if the timeout really works but you'll do this after you speed up the test so you won't have to wait so long for it to time out. Having to wait five seconds for a successful test is pretty tedious. It's enough to make you reluctant to run unit tests at all, so your next job is to create a tool to speed them up. You'll use a mocktask.sleep to inject a time dependency so you can set time to go faster in your tests. In blabber model, add a sleep property. You'll use this property to store the sleeping function you want the model to use. When you're not running unit tests, this is just the old nanosecond based task.sleep. For speeding up unit tests, this version of sleep is more convenient. You can easily speed up your tests by a factor of a billion. Now add this property to the countdown method. You're about to substitute the mock sleep function for task.sleep. In the async stream closure, now by default, blabber model behaves exactly the same way as before, but you can override sleep in your tests to make them run faster. In blabber tests, add this line to the end of your model definition before return model. Your test implementation of sleep takes the parameter pass to the function, divides it by a billion and calls task.sleep nanoseconds with the result. Effectively, you still implement the same workflow as before and provide the same suspension point at the right point in the execution. The only difference is that you run the code a billion times faster. Press Command + U to run both tests. Much faster, from over five seconds to less than 0.02 seconds. Now to see if timeout task really times out. Reduce wait time to one second, and increase the expected number of requests to five and press Command + U. And it works, your test timed out after one second. Change prefix back to four and run the test again. Your tests succeed. Set the wait time back to 10. And there you have some handy tools for testing asynchronous code. It's time to wrap up this part of the course before moving on to task groups and actors.