Charles Nyisztor here,
and I welcome you to this new video on Swift 3 programming.
In this video, I am going to show you two ways to test asynchronous code using XCode 8 and Swift 3.
Testing your code is important.
The presence of unit tests shows that you really care about the quality of your code. High test coverage indicates that you’ll have less bugs to be fixed, and your codebase is easier to maintain.
Testing asynchronous code used to be challenging. Developers had to use all kind of workarounds and 3rd party frameworks to implement unit tests for their asynchronous calls.
XCode’s unit testing framework has improved a lot, including its capability to test asynchronous code.
The XCTest framework provides expectations for testing asynchronous code. You can set up an expectation, then start your asynchronous code, and wait for it to finish. You must notify the test framework when the asynchronous call completes by marking the expectation as fulfilled.
Another option is to use semaphores; the approach is similar to using expectations: you set up a semaphore, then start the asynchronous call, and wait on the semaphore. When the asynchronous task completes, you signal the semaphore, which unblocks the thread and execution can continue.
In the following demo I am going to show you both solutions using a demo project written in Swift 3 and XCode 8.
Now, to demonstrate the testing of asynchronous calls, we will implement a Swift framework.
Let’s call it Downloader; the Downloader class exposes a single static method, which downloads data from a given URL.
The download method is asynchronous; the payload URL, the URL response, and the error are returned via a completion closure.
I instantiate a URLSession download task; for this, I need a URLSession object.
Let’s create a URLSession object with a default session configuration.
The URLSession downloadTask call creates a URLSessionDownloadTask instance; the method expects a URL and a completion closure. The completion handler used in our download method has the same signature as the one used by the downloadTask method, so we can simply pass it in as a parameter. This completion block will be invoked when the download task execution finishes.
It either returns with a link to a file which holds the downloaded data, or a non-nil error instance in the case of issues that may have occurred.
We must call resume() on the newly created download task to start it.
Now one important aspect we haven’t paid attention to yet is the thread-safety of the download method; we must ensure that it will work as expected even when invoked from several threads in parallel.
I will use a serial queue to protect the download method from concurrent access. The block of code which instantiates and resumes the download task is submitted to our serial dispatch queue for synchronous execution. Even if our download method is invoked from several threads in parallel, the creation and starting of download tasks won’t interfere, since the sync function does not return until the block is finished.
Ok, now comes the unit testing part.
First, I show you how to test asynchronous code using expectations.
We create the XCTestExpectation instance; it will be marked as fulfilled when the asynchronous task in our test completes. We are going to execute a fake download from the well-known test server httpbin.org. I really like this free server resource, since it is reliable, easy to use and accepts a variety of HTTP calls. Visit httpbin.org to check all the services it provides.
As you may already know, the download method is asynchronous; if we just execute it, it would simply return instantly, rendering our test method useless. The waitForExpectations call will run the run loop as long as the expectation is fulfilled or until the timeout expires; in other words, the test method is blocked upon calling waitForExpectations.
You should set the timeout to a reasonable value; small values may prevent your network task from completing. I set it to 10 seconds for our tests.
We should not forget to mark the expectation as fulfilled in the completion closure of our asynchronous download call.
We are ready to go now. Let’s execute the test: … all green, great!
I am going to debug the test method to see what’s happening.
After calling the download method, the execution jumps to the waitForExpectations call.
Shortly after that the download task finishes, and the completion closure is invoked. As the expectation is fulfilled, the test executor thread is unblocked, and the test method completes successfully.
Ok, now let’s implement the test method which is using a semaphore. The snippet for downloading data is the same as before,
I am creating a dispatch semaphore object. Dispatch semaphores are instantiated with a counter value, which controls the number of execution contexts that can access a given resource.
The semaphore wait() will decrease the counter; if the counter is less than zero, the function waits for a signal to occur before returning. This is a so-called counting semaphore implementation.
Since I initialized the counter to zero when creating the semaphore, the wait() function will decrease the counter to -1, and the test method won’t proceed. I set a timeout value of 5 seconds for the wait function.
Finally, I signal the semaphore at the end of the completion closure of the download method.
All right, let’s execute our test. Again, I set some breakpoints to showcase the execution flow. After the call to our download method, the control jumps to semaphore.wait in line 57. Shortly after that, the download completion handler is invoked.
When the semaphore signal gets called, the test continues execution and completes successfully.
I just demoed two ways to test your asynchronous code. Both the XCTestExpectation and the semaphore approach did their job. I’d recommend the first, XCTestExpectation based solution – except if you need to manipulate the run loop during your unit tests, which is highly unlikely; however, if you really have to mess with the run loop in your unit tests, then you’ll have to use semaphores to test your asynchronous calls.
The demo project is available on Github. You can find the link in the description of this video.
Best of luck – and don’t forget to subscribe if you are interested in similar topics.
Also, feel free to visit my website and check out my apps and some other cool stuff.