2013-03-25 24 views
14

Sono di fronte a un problema mentre l'unità verifica una chiamata asincrona in iOS. (Anche se sta funzionando bene in controller di vista.)Test di chiamata asincrona in unità in iOS

Qualcuno ha affrontato questo problema prima? Ho provato a utilizzare una funzione di attesa, ma sto ancora affrontando lo stesso problema.

Si prega di suggerire un esempio di un buon modo per farlo.

risposta

0

Prova quadro KIWI. È potente e potrebbe aiutarti con altri tipi di test.

27

Avrete bisogno di girare il runloop fino a quando non viene richiamata la richiamata. Assicurati che venga richiamato sulla coda principale, però.

Prova questo:

__block BOOL done = NO; 
doSomethingAsynchronouslyWithBlock(^{ 
    done = YES; 
}); 

while(!done) { 
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
} 

È anche possibile utilizzare un semaforo (esempio qui sotto), ma preferisco girare la runloop per consentire blocchi asincroni inviati alla coda principale da elaborare.

dispatch_semaphore_t sem = dispatch_semaphore_create(0); 
doSomethingAsynchronouslyWithBlock(^{ 
    //... 
    dispatch_semaphore_signal(sem); 
}); 

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 
+0

thanx ..è davvero aiutato –

+1

Per coloro che hanno problemi con l'approccio del ciclo di esecuzione: non funzionerà correttamente: il metodo 'runMode: beforeDate:' non verrà restituito fino a dopo l'elaborazione dell'evento di una sorgente.Questo potrebbe non succedere mai (a meno che il test unitario non lo faccia esplicitamente nel gestore del completamento in qualche modo);) – CouchDeveloper

+0

Ho usato la combinazione della tua soluzione con una Notifica che è già stata chiamata al termine del mio metodo. Grazie! –

1

Suggerisco di dare un'occhiata allo tests of Facebook-ios-sdk. È un buon esempio di come testare il test delle unità asincrone su iOS, anche se personalmente ritengo che i test asincroni debbano essere interrotti nei test di sincronizzazione.

FBTestBlocker: un blocco che impedisce l'interruzione del thread corrente con timeout specificato. Puoi trascinare e rilasciare questo nel tuo progetto, ma devi rimuovere le cose relative a OCMock se non lo hai nel tuo progetto.

FBTestBlocker.h

FBTestBlocker.m

FBURLConnectionTests: esempi di test si dovrebbe guardare.

FBURLConnectionTests.h

FBURLConnectionTests.m

Questo frammento di codice dovrebbe darvi qualche idea

- (void)testExample 
{ 
    FBTestBlocker *_blocker = [[FBTestBlocker alloc] initWithExpectedSignalCount:1]; 
    __block BOOL excuted = NO; 
    [testcase test:^(BOOL testResult) { 
     XCTAssert(testResult, @"Should be true"); 
     excuted = YES; 
     [_blocker signal]; 
    }]; 

    [_blocker waitWithTimeout:4]; 
    XCTAssertTrue(excuted, @"Not executed"); 
} 
3

AGAsyncTestHelper è una macro C per la scrittura di unit test con operazioni asincrone e funziona sia con SenTestingKit e XCTest.

semplice e al punto

- (void)testAsyncBlockCallback 
{ 
    __block BOOL jobDone = NO; 

    [Manager doSomeOperationOnDone:^(id data) { 
     jobDone = YES; 
    }]; 

    WAIT_WHILE(!jobDone, 2.0); 
} 
3

Ecco un'altra alternativa, XCAsyncTestCase, che funziona bene con OCMock se avete bisogno di usarlo. È basato sul tester asincrono di GHUnit, ma utilizza invece il normale framework XCTest. Completamente compatibile con Xcode Bot.

https://github.com/iheartradio/xctest-additions

L'uso è lo stesso, solo l'importazione e sottoclasse XCAsyncTestCase.

@implementation TestAsync 
- (void)testBlockSample 
{ 
    [self prepare]; 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){ 
     sleep(1.0); 
     [self notify:kXCTUnitWaitStatusSuccess]; 
    }); 
    // Will wait for 2 seconds before expecting the test to have status success 
    // Potential statuses are: 
    // kXCTUnitWaitStatusUnknown, initial status 
    // kXCTUnitWaitStatusSuccess, indicates a successful callback 
    // kXCTUnitWaitStatusFailure, indicates a failed callback, e.g login operation failed 
    // kXCTUnitWaitStatusCancelled, indicates the operation was cancelled 
    [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:2.0]; 
} 
0

vi consiglio di connessione semaforo + runloop, ho anche il metodo che prendono blocco scritto:

// Set the flag to stop the loop 
#define FLEND() dispatch_semaphore_signal(semaphore); 

// Wait and loop until flag is set 
#define FLWAIT() WAITWHILE(dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) 

// Macro - Wait for condition to be NO/false in blocks and asynchronous calls 
#define WAITWHILE(condition) \ 
do { \ 
while(condition) { \ 
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; \ 
} \ 
} while(0) 

metodo:

typedef void(^FLTestAsynchronousBlock)(void(^completion)(void)); 

void FLTestAsynchronous(FLTestAsynchronousBlock block) { 
    FLSTART(); 
    block(^{ 
     FLEND(); 
    }); 
    FLWAIT(); 
}; 

e chiamare

FLTestAsynchronous(^(void(^completion)()){ 

    [networkManager signOutUser:^{ 
     expect(networkManager.currentUser).to.beNil(); 
     completion(); 
    } errorBlock:^(NSError *error) { 
     expect(networkManager.currentUser).to.beNil(); 
     completion(); 
    }]; 

}); 
7

penso molte delle soluzioni suggerite i n questo post ha il problema che se l'operazione asincrona non completa il flag "done" non viene mai impostato e il test si bloccherà per sempre.

Ho utilizzato con successo questo approccio in molti dei miei test.

- (void)testSomething { 
    __block BOOL done = NO; 

    [obj asyncMethodUnderTestWithCompletionBlock:^{ 
     done = YES; 
    }]; 

    XCTAssertTrue([self waitFor:&done timeout:2], 
        @"Timed out waiting for response asynch method completion"); 
} 


- (BOOL)waitFor:(BOOL *)flag timeout:(NSTimeInterval)timeoutSecs { 
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs]; 

    do { 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate]; 
     if ([timeoutDate timeIntervalSinceNow] < 0.0) { 
      break; 
     } 
    } 
    while (!*flag); 
    return *flag; 
} 
13

Ecco Apple's description di supporto nativo per il test asincrona.

TL; manuale DR:

Guarda XCTextCase+AsynchronousTesting.h

C'è di classe speciale XCTestExpectation con un solo metodo pubblico: - (void)fulfill;

Si dovrebbe init un'istanza di questa classe e, in caso di chiamata di successo Metodo fulfill. In caso contrario, il test fallirà dopo timeout che si specifica in che modo:

- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(XCWaitCompletionHandler)handlerOrNil; 

Esempio:

- (void)testAsyncMethod 
{ 

    //Expectation 
    XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method Works Correctly!"]; 

    [MyClass asyncMethodWithCompletionBlock:^(NSError *error) {   
     if(error) 
      NSLog(@"error is: %@", error); 
     else 
      [expectation fulfill]; 
    }]; 

    //Wait 1 second for fulfill method called, otherwise fail:  
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { 

     if(error) 
     { 
      XCTFail(@"Expectation Failed with error: %@", error); 
     } 

    }]; 
} 
2

Sam Brodkin già dato il right answer.

Solo per rendere la risposta migliore a prima vista, porto qui il codice di esempio.

Utilizzare XCTestExpectation.

// Test that the document is opened. Because opening is asynchronous, 
// use XCTestCase's asynchronous APIs to wait until the document has 
// finished opening. 

- (void)testDocumentOpening 
{ 
    // Create an expectation object. 
    // This test only has one, but it's possible to wait on multiple expectations. 
    XCTestExpectation *documentOpenExpectation = [self expectationWithDescription:@"document open"]; 

    NSURL *URL = [[NSBundle bundleForClass:[self class]] 
          URLForResource:@"TestDocument" withExtension:@"mydoc"]; 
    UIDocument *doc = [[UIDocument alloc] initWithFileURL:URL]; 
    [doc openWithCompletionHandler:^(BOOL success) { 
     XCTAssert(success); 
     // Possibly assert other things here about the document after it has opened... 

     // Fulfill the expectation-this will cause -waitForExpectation 
     // to invoke its completion handler and then return. 
     [documentOpenExpectation fulfill]; 
    }]; 

    // The test will pause here, running the run loop, until the timeout is hit 
    // or all expectations are fulfilled. 
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { 
     [doc closeWithCompletionHandler:nil]; 
    }]; 
} 
0

è possibile utilizzare asincrona API chiamata in rapida come questo

private let serverCommunicationManager : ServerCommunicationManager = { 
    let instance = ServerCommunicationManager() 
    return instance 
}() 

var expectation:XCTestExpectation? 
func testAsyncApiCall() { 
    expectation = self.expectation(description: "async request") 

    let header = ["Authorization":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImQ4MmY1MTcxNzI4YTA5MjI3NWIzYWI3OWNkOTZjMGExOTI4MmM2NDEyZjMyYWQzM2ZjMzY4NmU2MjlhOWY2YWY1NGE0MDI4MmZiNzY2NWQ3In0.eyJhdWQiOiIxIiwianRpIjoiZDgyZjUxNzE3MjhhMDkyMjc1YjNhYjc5Y2Q5NmMwYTE5MjgyYzY0MTJmMzJhZDMzZmMzNjg2ZTYyOWE5ZjZhZjU0YTQwMjgyZmI3NjY1ZDciLCJpYXQiOjE1MDg4MjU1NTEsIm5iZiI6MTUwODgyNTU1MSwiZXhwIjoxNTQwMzYxNTUxLCJzdWIiOiIiLCJzY29wZXMiOltdfQ.osoMQgiY7TY7fFrh5r9JRQLQ6AZhIuEbrIvghF0VH4wmkqRUE6oZWjE5l0jx1ZpXsaYUhci6EDngnSTqs1tZwFTQ3srWxdXns2R1hRWUFkAN0ri32W0apywY6BrahdtiVZa9LQloD1VRMT1_QUnljMXKsLX36gXUsNGU6Bov689-bCbugK6RC3n4LjFRqJ3zD9gvkRaODuOQkqsNlS50b5tLm8AD5aIB4jYv3WQ4-1L74xXU0ZyBTAsLs8LOwvLB_2B9Qdm8XMP118h7A_ddLo9Cyw-WqiCZzeZPNcCvjymNK8cfli5_LZBOyjZT06v8mMqg3zszWzP6jOxuL9H1JjBF7WrPpz23m7dhEwa0a-t3q05tc1RQRUb16W1WhbRJi1ufdMa29uyhX8w_f4fmWdAnBeHZ960kjCss98FA73o0JP5F0GVsHbyCMO-0GOHxow3-BqyPOsmcDrI4ay006fd-TJk52Gol0GteDgdntvTMIrMCdG2jw8rfosV6BgoJAeRbqvvCpJ4OTj6DwQnV-diKoaHdQ8vHKe-4X7hbYn_Bdfl52gMdteb3_ielcVXIaHmQ-Dw3E2LSVt_cSt4tAHy3OCd7WORDY8uek4Paw8Pof0OiuqQ0EB40xX5hlYqZ7P_tXpm-W-8ucrIIxgpZb0uh-wC3EzBGPjpPD2j9CDo"] 
    serverCommunicationManager.sendServerRequest(httpMethodType: .get, baseURL: "http://192.168.2.132:8000/api/v1/user-role-by-company-id/2", param: nil, header: header) { (isSuccess, msg , response) in 
     if isSuccess 
     { 
      let array = response as! NSArray 

      if array.count == 8 
      { 
       XCTAssertTrue(true) 
       self.expectation?.fulfill() 
      } 
      else 
      { 
       XCTAssertFalse(false) 
       XCTFail("array count fail") 
      } 
     } 
    } 
    waitForExpectations(timeout: 5) { (error) in 
     if let error = error{ 
      XCTFail("waiting with error: \(error.localizedDescription)") 
     } 
    } 
}