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

前端 未结 3 449
我在风中等你
我在风中等你 2020-12-12 18:15

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 al

相关标签:
3条回答
  • 2020-12-12 18:29

    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.

    0 讨论(0)
  • 2020-12-12 18:30

    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];
    }
    
    0 讨论(0)
  • 2020-12-12 18:39

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

    0 讨论(0)
提交回复
热议问题