Testing asynchronous call in unit test in iOS

后端 未结 12 1503
-上瘾入骨i
-上瘾入骨i 2020-12-24 08:51

I am facing a problem while unit testing an asynchronous call in iOS. (Although it is working fine in view controllers.)

Has anyone faced this issue before? I have t

相关标签:
12条回答
  • 2020-12-24 09:46

    Here's another alternative, XCAsyncTestCase, that works well with OCMock if you need to use it. It's based on GHUnit's async tester, but is uses the regular XCTest framework instead. Fully compatible with Xcode Bots.

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

    Usage is the same, just import and subclass 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 讨论(0)
  • 2020-12-24 09:50

    You'll need to spin the runloop until your callback is invoked. Make sure that it gets invoked on the main queue, though.

    Try this:

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

    You can also use a semaphore (example below), but I prefer to spin the runloop to allow asynchronous blocks dispatched to the main queue to be processed.

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    doSomethingAsynchronouslyWithBlock(^{
        //...
        dispatch_semaphore_signal(sem);
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    
    0 讨论(0)
  • 2020-12-24 09:51

    Try KIWI framework. It's powerful and might help you with other kinds of tests.

    0 讨论(0)
  • 2020-12-24 09:51

    Since Xcode 6 this built in to XCTest as a category:

    See https://stackoverflow.com/a/24705283/88164

    0 讨论(0)
  • 2020-12-24 09:56

    I recommend you connection semaphore + runloop, i also wrote method which take block:

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

    method:

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

    and call

    FLTestAsynchronous(^(void(^completion)()){
    
        [networkManager signOutUser:^{
            expect(networkManager.currentUser).to.beNil();
            completion();
        } errorBlock:^(NSError *error) {
            expect(networkManager.currentUser).to.beNil();
            completion();
        }];
    
    });
    
    0 讨论(0)
  • 2020-12-24 09:57

    Sam Brodkin already gave the right answer.

    Just to make the answer looks better at first sight, I bring the sample code here.

    Use 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 讨论(0)
提交回复
热议问题