Search code examples
iosswiftasynchronousxctestcase

Wait for multiple async calls in setUp() of XCTestCase


I need to perform multiple calls before letting the test() method run. I have a completion block, and I'm using waitForExpectations(). Since there is more than one async calls I'm using a counter. I let the expectation.fulfill() happen only when the counter reaches the number of calls.

override func setUp() {
    super.setUp()

    let exp = expectation(description: "waitForSetUp")
    var counter = 0

    // Issue an async request
    self.addEventToCalendar(title: "Test1", description: "Description test1", startDate: NSDate().addingTimeInterval(-36000), endDate: NSDate()){(success, error) in
        if (success) && (error == nil) {
            counter = counter + 1
            if(counter == 2){exp.fulfill()}
        }
    }

    self.addEventToCalendar(title: "Test2", description: "Description test2", startDate: NSDate(), endDate: NSDate().addingTimeInterval(36000)){(success, error) in
        if (success) && (error == nil) {
            counter = counter + 1
            if(counter == 2){exp.fulfill()}
        }
    }

    waitForExpectations(timeout: 40, handler: nil)        
}

This construction does not work. The test() method is sometimes run before the calls have returned (not always).

How can I make setUp() wait for returning multiple async calls?


Solution

  • I had a similar case. The solution that I end up doing was calling several functions, which add expectations for my prerequisites and setting the timeouts for the expectations to a reasonable values. In the completion handlers of the expectations I triggered the next step of my setup algorithm. After all preliminary steps pass I start the actual testing logic.

    Attaching link to the Apple Docs.

    EDIT: Please see the example code below :

    class CommonTests: XCTestCase {
        var validate: XCTestExpectation? = nil
    
        func testMytest() {        
          validate(completion: {
            loadSomeStuff(completion: { (list: [Stuff]?) in
              // actual test
            }
          })
        }
    
        func validate(completion: @escaping ()->()) {
            self.validateExpectation = self.expectation(description: "Setup")
            // async operation can be fired here
            // or if already started from somewhere else just wait for it to complete
    
    
            self.waitForExpectations(timeout: 60) { (error: Error?) in
                XCTAssert((error == nil), error?.localizedDescription ?? "Failed with unknown error")
                completion()
            }
        }
    
        func validateAsyncCompleted() {
          self.validateExpectation?.fulfill()
        }
    
        func loadStuff(completion: @escaping ([Stuff]?)->()) {
    
          // possible place for assertion or some other checks
    
          let expectation = self.expectation(description: "loading")
          DispatchQueue.global().async {
    
            let result: [Stuff]? = nil
            // load operation
    
            expectation.fulfill()
            completion(result)
          }
    
          self.waitForExpectations(timeout: 90) {  (error: Error?) in
            XCTAssert((error == nil), error?.localizedDescription ?? "load - failed with unknown error")
          }
        }
    }
    

    NOTE : There are 2 approaches for the expectations, the first expectation is saved in a variable, so it can be fulfilled from another function if needed, the other is created locally in a function body and fulfilled from a closure.