web-dev-qa-db-fra.com

Comment puis-je demander à XCTest d'attendre les appels asynchrones dans setUp avant d'exécuter les tests?

J'écris des tests d'intégration dans Xcode 6 pour accompagner mes tests unitaires et fonctionnels. XCTest a une méthode setUp () qui est appelée avant chaque test. Génial!

Il a également des XCTestException qui me permettent d'écrire des tests asynchrones. Aussi génial!

Cependant, je voudrais remplir ma base de données de test avec des données de test avant chaque test et setUp commence juste à exécuter des tests avant que l'appel de base de données asynchrone ne soit effectué.

Existe-t-il un moyen de faire configurer SetUp jusqu'à ce que ma base de données soit prête avant d'exécuter des tests?

Voici un exemple de ce que je fais maintenant. Puisque setUp revient avant que la base de données ne soit remplie, je dois dupliquer beaucoup de code de test à chaque 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)
        }
    }

}

Voici ce que j'aimerais:

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

    }

}
46
Brett Elliot

Il existe deux techniques pour exécuter des tests asynchrones. XCTestExpectation et sémaphores. Dans le cas de faire quelque chose d'asynchrone dans setUp, vous devez utiliser la technique du sémaphore:

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

Notez que pour que cela fonctionne, ce bloc onDone ne peut pas s'exécuter sur le thread principal (sinon vous bloquerez).


Si ce bloc onDone s'exécute sur la file d'attente principale, vous pouvez utiliser des boucles d'exécution:

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

Il s'agit d'un modèle très inefficace, mais selon la façon dont overwriteDatabase a été implémenté, il peut être nécessaire

Remarque, n'utilisez ce modèle que si vous savez que le bloc onDone s'exécute sur le thread principal (sinon vous devrez synchroniser la variable finished).

22
Rob

Plutôt que d'utiliser des sémaphores ou des boucles de blocage, vous pouvez utiliser le même waitForExpectationsWithTimeout:handler: fonction que vous utilisez dans vos cas de test asynchrones.

// 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];
}
94
RndmTsk

Swift 4.2

utilisez cette 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
    }
}

et une utilisation comme celle-ci:

func testShoudDeleteSection() {
        let tableView = TableViewSpy()
        sut.tableView = tableView

        sut.sectionDidDelete(at: 0)

        wait {
            XCTAssert(tableView.isReloadDataCalled, "Chcek relaod table view after section delete")
        }
    }

l'exemple ci-dessus n'est pas complet mais vous pouvez vous faire une idée. j'espère que cette aide.

1
moraei