在iOS中testingunit testing中的asynchronous调用

我在unit testingiOS中的asynchronous调用时遇到问题。 (虽然它在视图控制器中工作正常。)

有没有人遇到过这个问题? 我曾尝试使用等待function,但我仍然面临同样的问题。

请build议一个很好的方法来做到这一点的例子。

尝试KIWI框架。 它function强大,可能会帮助您进行其他types的testing。

你需要旋转runloop直到你的callback被调用。 确保它在主队列上被调用。

尝试这个:

__block BOOL done = NO; doSomethingAsynchronouslyWithBlock(^{ done = YES; }); while(!done) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } 

你也可以使用一个信号量(下面的例子),但是我更喜欢旋转runloop来允许分派到主队列的asynchronous块被处理。

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

这里是苹果公司对asynchronoustesting本地支持的描述 。

TL; DR手册:

看看XCTextCase+AsynchronousTesting.h

只有一个公共方法有特殊的类XCTestExpectation- (void)fulfill;

你应该初始化这个类的实例,并在成功的情况下调用fulfill方法。 否则,您的testing将在超时后失败,您在该方法中指定:

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

例:

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

我认为这篇文章中的很多build议的解决scheme都存在这样的问题:如果asynchronous操作没有完成,“完成”标志从不设置,testing将永远挂起。

我在许多testing中成功地使用了这种方法。

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

由于Xcode 6内置于XCTest作为一个类别:

请参阅https://stackoverflow.com/a/24705283/88164

这里有另外一个替代scheme,XCAsyncTestCase,如果你需要使用OCMock,它可以很好地工作。 它基于GHUnit的asynchronoustesting器,而是使用常规的XCTest框架。 完全兼容Xcode机器人。

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

用法是一样的,只是导入和子类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, eg login operation failed // kXCTUnitWaitStatusCancelled, indicates the operation was cancelled [self waitForStatus:kXCTUnitWaitStatusSuccess timeout:2.0]; } 

AGAsyncTestHelper是一个Cmacros,用于编写与asynchronous操作相关的unit testing,并与SenTestingKit和XCTest一起使用。

简单而重要

 - (void)testAsyncBlockCallback { __block BOOL jobDone = NO; [Manager doSomeOperationOnDone:^(id data) { jobDone = YES; }]; WAIT_WHILE(!jobDone, 2.0); } 

山姆·布罗德金已经给出了正确的答案 。

只是为了让答案看起来更好看,我把这里的示例代码。

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

我build议你应该看看Facebook-ios-sdktesting 。 这是如何在iOS上testingasynchronousunit testing的一个很好的例子,虽然我个人认为asynchronoustesting应该进入同步testing。

FBTestBlocker :阻止当前线程退出指定超时的阻止程序。 你可以把它拖放到你的项目中,但如果你没有这个项目,你需要删除OCMock相关的东西。

FBTestBlocker.h

FBTestBlocker.m

FBURLConnectionTests :你应该看看testing的例子。

FBURLConnectionTests.h

FBURLConnectionTests.m

这段代码应该给你一些想法

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

我build议你连接信号量+ runloop,我也写了块的方法:

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

方法:

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

并打电话

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

如果您使用的是XCode 6,则可以像这样testingasynchronousnetworking呼叫:

XCT 6中的XCTest和asynchronoustesting

你可以像这样用swift来调用asynchronousAPI

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