Prueba de unidad iOS: espere el intervalo de time con expectativas

Estoy intentando actualizar mis testings de unidades asíncronas para usar la nueva interfaz XCTestExpectation en lugar de girar manualmente el bucle de ejecución.

Las testings de mi unidad utilizaron previamente las funciones waitForBlock , finishBlock y waitForTimeInterval: que es simplemente un método de conveniencia que llamó a finishBlock después del time especificado. Estoy intentando actualizar esta configuration para usar las expectativas.

Las testings que utilizaban la semántica waitForBlock + finishBlock funcionan como se esperaba después de ser reemplazadas con waitForExpectationsWithTime:handler: y fulfill , pero mi solución para replace waitForTimeInterval: no parece estar funcionando.

 - (void)waitForTimeInterval:(NSTimeInterval)delay { XCTestExpectation *expectation = [self expectationWithDescription:@"wait"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [expectation fulfill]; }); [self waitForExpectationsWithTimeout:delay + 1 handler:nil]; } 

Editar:

Parece que ese código realmente funciona … así que este fue probablemente solo Xcode 6 atormentándome esta tarde.


Siento que debería ser bastante sencillo: cree una expectativa, configure un bloque asíncrono que cumpla y espere. Sin embargo, el bloque dispatch_after nunca se invoca.

Mi corazon es que waitForExpectationsWithTimeout:handler: bloquea su subprocess actual, que es la queue principal, por lo que el ciclo de ejecución nunca llega a sus bloques asíncronos. Eso parece razonable, pero tengo problemas para encontrar una forma diferente de implementar esta funcionalidad.

Estoy buscando 1) información adicional sobre XCTestExpectation que pueda revelar una solución alternativa, o 2) una idea diferente para implementar esta funcionalidad.

He usado XCTestExpectation en una serie de proyectos para todo tipo de web asíncrona y material GCD … AFAIK, algo parecido a lo siguiente es lo que parece ser el uso más común:

 - (void)testExample { // create the expectation with a nice descriptive message XCTestExpectation *expectation = [self expectationWithDescription:@"The request should successfully complete within the specific timeframe."]; // do something asyncronously [someObject doAsyncWithCompletionHandler:^(NSInteger yourReturnValue) { // now that the async task it done, test whatever states/values you expect to be after this is // completed NSInteger expectedValue = 42; XCTAssert(yourReturnValue == expectedValue, @"The returned value doesn't match the expected value."); // fulfill the expectation so the tests can complete [expectation fulfill]; }]; // wait for the expectations to be called and timeout after some // pnetworkingefined max time limit, failing the test automatically NSTimeInterval somePnetworkingefinedTimeout = 3; [self waitForExpectationsWithTimeout:somePnetworkingefinedTimeout handler:nil]; } 

dispatch_async(dispatch_get_main_queue(), ...) no parece funcionar en las testings de interfaz de usuario Xcode 7, nunca se llama al controller de finalización. performSelector ya no está disponible en Swift, pero hay otras dos soluciones:

  1. Usando un timer

     var waitExpectation: XCTestExpectation? func wait(duration: NSTimeInterval) { waitExpectation = expectationWithDescription("wait") NSTimer.scheduledTimerWithTimeInterval(duration, target: self, selector: Selector("onTimer"), userInfo: nil, repeats: false) waitForExpectationsWithTimeout(duration + 3, handler: nil) } func onTimer() { waitExpectation?.fulfill() } 
  2. Ejecute el bloque en la queue global (funciona, pero probablemente no sea seguro, ya que no está documentado en ningún lugar donde XCTestExpectation sea seguro para subprocesss).

     func wait(duration: NSTimeInterval) { let expectation = expectationWithDescription("wait") let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(duration * Double(NSEC_PER_SEC))) dispatch_after(dispatchTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { expectation.fulfill() } waitForExpectationsWithTimeout(duration + 3, handler: nil) } 

Aparentemente usando performSelector:withObject:afterDelay: funciona como se esperaba, aunque todavía no estoy seguro de por qué. Si alguien tiene una idea de por qué el dispatch_after de GCD no funciona, brinde una respuesta adicional y lo aceptaré. Por ahora, esta configuration parece funcionar como se esperaba:

 - (void)waitForTimeInterval:(NSTimeInterval)delay { XCTestExpectation *expectation = [self expectationWithDescription:@"wait"]; [self performSelector:@selector(fulfillExpectation:) withObject:expectation afterDelay:delay]; [self waitForExpectationsWithTimeout:delay + 1 handler:nil]; } - (void)fulfillExpectation:(XCTestExpectation *)expectation { [expectation fulfill]; } 

En uno de los casos de testing de mi Unidad, necesitaba probar el funcionamiento de un método en mi código de aplicación principal, que debería activar un timer en aproximadamente 1 segundo para llamar a otro método en la aplicación. Utilicé XCTestExpectation wait y DispatchQueue.asyncAfter como un mecanismo para detenerme y esperar antes de verificar el resultado. El siguiente código es un fragment en Swift 3:

  <call the main app method which will trigger a timer event> // wait let expectation = XCTestExpectation(description: "test") DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) { expectation.fulfill() } wait(for: [expectation], timeout: 2.5) <check the result of the timer event>