How can I get XCTest to wait for async calls in setUp before tests are run?

XcodeUnit TestingSwiftAsynchronousXctest

Xcode Problem Overview


I'm writing integration tests in Xcode 6 to go alongside my unit and functional tests. XCTest has a setUp() method that gets called before every test. Great!

It also has XCTestException's which let me write async tests. Also great!

However, I would like to populate my test database with test data before every test and setUp just starts executing tests before the async database call is done.

Is there a way to have setUp wait until my database is ready before it runs tests?

Here's an example of what I have do now. Since setUp returns before the database is done populating I have to duplicate a lot of test code every test:

func test_checkSomethingExists() {

    let expectation = expectationWithDescription("")
    var expected:DatabaseItem

    // Fill out a database with data. 
    var data = getData()
    overwriteDatabase(data, {
      // Database populated.
      // Do test... in this pseudocode I just check something...
      db.retrieveDatabaseItem({ expected in

        XCTAssertNotNil(expected)
        
        expectation.fulfill()
      })
    })
 
    waitForExpectationsWithTimeout(5.0) { (error) in
        if error != nil {
            XCTFail(error.localizedDescription)
        }
    }

}

Here's what I would like:

class MyTestCase: XCTestCase {
    
    override func setUp() {
        super.setUp()
        
        // Fill out a database with data. I can make this call do anything, here
        // it returns a block.
        var data = getData()
        db.overwriteDatabase(data, onDone: () -> () {

           // When database done, do something that causes setUp to end 
           // and start running tests

        })        
    }
 
    func test_checkSomethingExists() {

        let expectation = expectationWithDescription("")
        var expected:DatabaseItem

      
          // Do test... in this pseudocode I just check something...
          db.retrieveDatabaseItem({ expected in

            XCTAssertNotNil(expected)
            
            expectation.fulfill()
        })
     
        waitForExpectationsWithTimeout(5.0) { (error) in
            if error != nil {
                XCTFail(error.localizedDescription)
            }
        }

    }
  
}

Xcode Solutions


Solution 1 - Xcode

Rather than using semaphores or blocking loops, you can use the same waitForExpectationsWithTimeout:handler: function you use in your async test cases.

// Swift
override func setUp() {
    super.setUp()

    let exp = expectation(description: "\(#function)\(#line)")

    // Issue an async request
    let data = getData()
    db.overwriteDatabase(data) {
        // do some stuff
        exp.fulfill()
    }

    // Wait for the async request to complete
    waitForExpectations(timeout: 40, handler: nil)
}

// Objective-C
- (void)setUp {
    [super setUp];

    NSString *description = [NSString stringWithFormat:@"%s%d", __FUNCTION__, __LINE__];
    XCTestExpectation *exp = [self expectationWithDescription:description];

    // Issue an async request
    NSData *data = [self getData];
    [db overwriteDatabaseData: data block: ^(){
        [exp fulfill];
    }];        

    // Wait for the async request to complete
    [self waitForExpectationsWithTimeout:40 handler: nil];
}

Solution 2 - Xcode

There are two techniques for running asynchronous tests. XCTestExpectation and semaphores. In the case of doing something asynchronous in setUp, you should use the semaphore technique:

override func setUp() {
    super.setUp()
    
    // Fill out a database with data. I can make this call do anything, here
    // it returns a block.
    
    let data = getData()
    
    let semaphore = DispatchSemaphore(value: 0)

    db.overwriteDatabase(data) {

        // do some stuff
        
        semaphore.signal()
    }
    
    semaphore.wait()
}

Note, for that to work, this onDone block cannot run on the main thread (or else you'll deadlock).


If this onDone block runs on the main queue, you can use run loops:

override func setUp() {
    super.setUp()

    var finished = false

    // Fill out a database with data. I can make this call do anything, here
    // it returns a block.
    
    let data = getData()
    
    db.overwriteDatabase(data) {

        // do some stuff
        
        finished = true
    }
    
    while !finished {
        RunLoop.current.run(mode: .default, before: Date.distantFuture)
    }
}

This is a very inefficient pattern, but depending upon how overwriteDatabase was implemented, it might be necessary

Note, only use this pattern if you know that onDone block runs on the main thread (otherwise you'll have to do some synchronization of finished variable).

Solution 3 - Xcode

Swift 4.2

use this extension:

import XCTest

extension XCTestCase {
    func wait(interval: TimeInterval = 0.1 , completion: @escaping (() -> Void)) {
        let exp = expectation(description: "")
        DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
            completion()
            exp.fulfill()
        }
        waitForExpectations(timeout: interval + 0.1) // add 0.1 for sure asyn after called
    }
}

and usage like this:

func testShoudDeleteSection() {
        let tableView = TableViewSpy()
        sut.tableView = tableView
        
        sut.sectionDidDelete(at: 0)
        
        wait {
            XCTAssert(tableView.isReloadDataCalled, "Chcek relaod table view after section delete")
        }
    }

example above isn't complete but you can get the idea. hope this help.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBrett ElliotView Question on Stackoverflow
Solution 1 - XcodeRndmTskView Answer on Stackoverflow
Solution 2 - XcodeRobView Answer on Stackoverflow
Solution 3 - XcodemoraeiView Answer on Stackoverflow