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

Brett Elliot picture Brett Elliot · Apr 8, 2015 · Viewed 25.4k times · Source

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)
            }
        }

    }

}

Answer

RndmTsk picture RndmTsk · Apr 13, 2016

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];
}