What is the conventional way to unit test asynchronous operations?
Background:
When my app is initialised it makes a call to CloudKit and updates its internal data source with the results:
import Foundation
import CloudKit
class CountdownItemModel {
// MARK: - Properties
var countdownItems: [CountdownItem] = []
static let sharedInstance = CountdownItemModel()
let container: CKContainer
let database: CKDatabase
// MARK: - Initializers
init() {
container = CKContainer.default()
database = container.privateCloudDatabase
getAllCountdownItems()
}
// MARK - CloudKit Methods
func getAllCountdownItems() {
let query = CKQuery(recordType: "CountdownItem", predicate: NSPredicate(value: true))
database.perform(query, inZoneWith: nil) {
(results, error) in
if (error != nil) {
print("[ERROR] Error in getAllCountdownItems\n", error!)
}
else {
self.countdownItems.removeAll(keepingCapacity: true)
if let records = results {
for record in records {
let countdownItem = CountdownItem(record: record)
self.countdownItems.append(countdownItem)
}
}
}
}
}
}
Using X-Code's unit testing functionality I've written a test to check that the data from iCloud has arrived, but this test runs before the query has returned, and therefore fails.
import XCTest
@testable import countdown
class countdownTests: XCTestCase {
let model = CountdownItemModel.sharedInstance
override func setUp() {
super.setUp()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testItemsCountGreatThanZero() {
sleep(10) // <- DIRTY FIX/WORKAROUND
XCTAssert(model.countdownItems.count > 0)
}
}
As you can see, I've worked around this by inserting a sleep into the test, but this strikes be as a bit messy, and veers into the realm of performance testing rather than unit testing.
Is there a generally accepted approach to this?
When doing async test, you can work with expectation. You can use code like this:
func testSomething() {
let exp = expectation(description: "anAsyncCall")
DoSomethingAsync() {
exp.fulfill()
}
waitForExpectations(timeout: 10) { error in
XCTAssertNil(error, "\(error)")
}
}
In this case if the async call is finished within 10 seconds then the test will succeed.