Unit testing Parse framework iOS

≯℡__Kan透↙ 提交于 2019-11-29 08:01:59

If you don't mind to utilize a third party library, you may use the following approach:

#import <XCTest/XCTest.h>
#import <RXPromise/RXPromise.h>
...

@interface PFUserTests : XCTestCase
@end

@implementation PFUserTests

// helper:
- (RXPromise*) saveUser:(User*)user {
    RXPromise* promise = [[RXPromise alloc] init];
    [PFUser saveUser:user withBlock:^(Bool success, NSError*error){
        if (success) {
            [promise fulfillWithValue:@"OK"];
        }
        else {
            [promise rejectWithReason:error];
        }
    }];
    return promise;
}


// tests:

-(void) testSaveUser 
{
    User* user = ... ;
    RXPromise* promise = [self saveUser:user];

    // set a timeout:
    [promise setTimeout:5.0];

    [promise.thenOn(dispatch_get_main_queue(), ^id(id result) {
        XCTAssertTrue([result isEqualToString:@"OK"], @"");
        XCTAssertTrue( ... , @"");
        return nil;
    }, ^id(NSError* error) {
        // this may fail either due to a timeout or when saveUser fails:
        XCTFail(@"Method failed with error: %@", error);
        return nil;
    }) runLoopWait];  // suspend the run loop, until after the promise gets resolved 

}

A few notes:

You can execute the success or failure handler (which are registered for the promise with the thenOn statement) on any queue, just specify the queue. This does not dead lock the main thread, even though the handler gets explicitly executed on the main thread, and the test runs as well on the main thread (thanks to the run loop mechanism).

The method runLoopWait will enter a run loop, and only return once the promise has been resolved. This is both, effective and efficient.

A promise accepts a timeout. If the timeout expires, the promise will be "rejected" (that is resolved) with a corresponding error.

IMO, promises are an invaluable tool for handling common asynchronous programming problems - not just a helper for unit tests. (See also: wiki article Futures and promises.

Please note, I'm the author of the RXPromise library, and thus I'm totally biased ;)

There are other Promise implementations for Objective-C as well.

Turns out it was just due to my lack of unit testing understanding. After some research on mock objects, stubs and the like I came up with:

- (void)testSaveUser {
    id userModelMock = [OCMockObject mockForClass:[UserModel class]];
    id userControllerMock = [OCMockObject partialMockForObject:self.userController];

    [[userModelMock expect] saveInBackgroundWithBlock:[OCMArg any]];
    [userControllerMock saveUser:userModelMock withBlock:[OCMArg any]];

    [userModelMock verify];
}

With the macro WAIT_WHILE(<expression>, <time_limit>) in https://github.com/hfossli/AGAsyncTestHelper/ you can write

- (void)testSaveUser
{
    __block BOOL saved = NO;
    [PFUser saveUser:userModelMock withBlock:^(BOOL success, NSError *error) {
        saved = YES;
    }];
    WAIT_WHILE(!saved, 10.0, @"Failed to save user within 10 seconds timeframe);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!